[pigeon] Minor fixes to NNBD type handling in Dart (#1543)
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index d848818..d3d130a 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 3.0.2
+
+* Fixes non-nullable classes and enums as fields.
+* Fixes nullable collections as return types.
+
## 3.0.1
* Enables NNBD for the Pigeon tool itself.
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 982044c..3625b36 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -192,10 +192,11 @@
final String returnType = _makeGenericTypeArguments(func.returnType);
final String castCall = _makeGenericCastCall(func.returnType);
const String accessor = 'replyMap[\'${Keys.result}\']';
- final String unwrapper = func.returnType.isNullable ? '' : '!';
+ final String nullHandler =
+ func.returnType.isNullable ? (castCall.isEmpty ? '' : '?') : '!';
final String returnStatement = func.returnType.isVoid
? 'return;'
- : 'return ($accessor as $returnType?)$unwrapper$castCall;';
+ : 'return ($accessor as $returnType?)$nullHandler$castCall;';
indent.format('''
final Map<Object?, Object?>? replyMap =\n\t\tawait channel.send($sendArgument) as Map<Object?, Object?>?;
if (replyMap == null) {
@@ -459,13 +460,14 @@
);
for (final NamedType field in klass.fields) {
indent.write('pigeonMap[\'${field.name}\'] = ');
+ final String conditional = field.type.isNullable ? '?' : '';
if (customClassNames.contains(field.type.baseName)) {
indent.addln(
- '${field.name}?.encode();',
+ '${field.name}$conditional.encode();',
);
} else if (customEnumNames.contains(field.type.baseName)) {
indent.addln(
- '${field.name}?.index;',
+ '${field.name}$conditional.index;',
);
} else {
indent.addln('${field.name};');
@@ -478,15 +480,29 @@
void writeDecode() {
void writeValueDecode(NamedType field) {
if (customClassNames.contains(field.type.baseName)) {
- indent.format('''
+ final String nonNullValue =
+ "${field.type.baseName}.decode(pigeonMap['${field.name}']!)";
+ indent.format(
+ field.type.isNullable
+ ? '''
pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']!)
-\t\t: null''', leadingSpace: false, trailingNewline: false);
+\t\t? $nonNullValue
+\t\t: null'''
+ : nonNullValue,
+ leadingSpace: false,
+ trailingNewline: false);
} else if (customEnumNames.contains(field.type.baseName)) {
- indent.format('''
+ final String nonNullValue =
+ "${field.type.baseName}.values[pigeonMap['${field.name}']! as int]";
+ indent.format(
+ field.type.isNullable
+ ? '''
pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']! as int]
-\t\t: null''', leadingSpace: false, trailingNewline: false);
+\t\t? $nonNullValue
+\t\t: null'''
+ : nonNullValue,
+ leadingSpace: false,
+ trailingNewline: false);
} else if (field.type.typeArguments.isNotEmpty) {
final String genericType = _makeGenericTypeArguments(field.type);
final String castCall = _makeGenericCastCall(field.type);
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 65397d9..a22c7e4 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -7,8 +7,8 @@
import 'dart:mirrors';
import 'ast.dart';
-/// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '3.0.1';
+/// The current version of pigeon. This must match the version in pubspec.yaml.\
+const String pigeonVersion = '3.0.2';
/// Read all the content from [stdin] to a String.
String readStdin() {
diff --git a/packages/pigeon/pigeons/non_null_fields.dart b/packages/pigeon/pigeons/non_null_fields.dart
index bbce336..aa96b87 100644
--- a/packages/pigeon/pigeons/non_null_fields.dart
+++ b/packages/pigeon/pigeons/non_null_fields.dart
@@ -12,11 +12,21 @@
String query;
}
+class ExtraData {
+ ExtraData({required this.detailA, required this.detailB});
+ String detailA;
+ String detailB;
+}
+
+enum ReplyType { success, error }
+
class SearchReply {
- SearchReply(this.result, this.error, this.indices);
+ SearchReply(this.result, this.error, this.indices, this.extraData, this.type);
String result;
String error;
List<int?> indices;
+ ExtraData extraData;
+ ReplyType type;
}
@HostApi()
diff --git a/packages/pigeon/pigeons/nullable_returns.dart b/packages/pigeon/pigeons/nullable_returns.dart
index 150d5d3..548e159 100644
--- a/packages/pigeon/pigeons/nullable_returns.dart
+++ b/packages/pigeon/pigeons/nullable_returns.dart
@@ -8,12 +8,12 @@
import 'package:pigeon/pigeon.dart';
@HostApi()
-abstract class NonNullHostApi {
+abstract class NullableReturnHostApi {
int? doit();
}
@FlutterApi()
-abstract class NonNullFlutterApi {
+abstract class NullableReturnFlutterApi {
int? doit();
}
@@ -26,3 +26,23 @@
abstract class NullableArgFlutterApi {
int doit(int? x);
}
+
+@HostApi()
+abstract class NullableCollectionReturnHostApi {
+ List<String?>? doit();
+}
+
+@FlutterApi()
+abstract class NullableCollectionReturnFlutterApi {
+ List<String?>? doit();
+}
+
+@HostApi()
+abstract class NullableCollectionArgHostApi {
+ List<String?> doit(List<String?>? x);
+}
+
+@FlutterApi()
+abstract class NullableCollectionArgFlutterApi {
+ List<String?> doit(List<String?>? x);
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
index 9dbcebe..0b7e513 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
@@ -12,6 +12,11 @@
import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
import 'package:flutter/services.dart';
+enum ReplyType {
+ success,
+ error,
+}
+
class SearchRequest {
SearchRequest({
required this.query,
@@ -33,22 +38,53 @@
}
}
+class ExtraData {
+ ExtraData({
+ required this.detailA,
+ required this.detailB,
+ });
+
+ String detailA;
+ String detailB;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['detailA'] = detailA;
+ pigeonMap['detailB'] = detailB;
+ return pigeonMap;
+ }
+
+ static ExtraData decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return ExtraData(
+ detailA: pigeonMap['detailA']! as String,
+ detailB: pigeonMap['detailB']! as String,
+ );
+ }
+}
+
class SearchReply {
SearchReply({
required this.result,
required this.error,
required this.indices,
+ required this.extraData,
+ required this.type,
});
String result;
String error;
List<int?> indices;
+ ExtraData extraData;
+ ReplyType type;
Object encode() {
final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
pigeonMap['result'] = result;
pigeonMap['error'] = error;
pigeonMap['indices'] = indices;
+ pigeonMap['extraData'] = extraData.encode();
+ pigeonMap['type'] = type.index;
return pigeonMap;
}
@@ -58,6 +94,8 @@
result: pigeonMap['result']! as String,
error: pigeonMap['error']! as String,
indices: (pigeonMap['indices'] as List<Object?>?)!.cast<int?>(),
+ extraData: ExtraData.decode(pigeonMap['extraData']!),
+ type: ReplyType.values[pigeonMap['type']! as int],
);
}
}
@@ -66,12 +104,15 @@
const _NonNullHostApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
- if (value is SearchReply) {
+ if (value is ExtraData) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
- } else if (value is SearchRequest) {
+ } else if (value is SearchReply) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
+ } else if (value is SearchRequest) {
+ buffer.putUint8(130);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -81,9 +122,12 @@
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
- return SearchReply.decode(readValue(buffer)!);
+ return ExtraData.decode(readValue(buffer)!);
case 129:
+ return SearchReply.decode(readValue(buffer)!);
+
+ case 130:
return SearchRequest.decode(readValue(buffer)!);
default:
@@ -137,12 +181,15 @@
const _NonNullFlutterApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
- if (value is SearchReply) {
+ if (value is ExtraData) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
- } else if (value is SearchRequest) {
+ } else if (value is SearchReply) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
+ } else if (value is SearchRequest) {
+ buffer.putUint8(130);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -152,9 +199,12 @@
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
- return SearchReply.decode(readValue(buffer)!);
+ return ExtraData.decode(readValue(buffer)!);
case 129:
+ return SearchReply.decode(readValue(buffer)!);
+
+ case 130:
return SearchRequest.decode(readValue(buffer)!);
default:
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
index cf07297..52cf1e5 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
-// Autogenerated from Pigeon (v2.0.4), do not edit directly.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
// @dart = 2.12
@@ -12,24 +12,24 @@
import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
import 'package:flutter/services.dart';
-class _NonNullHostApiCodec extends StandardMessageCodec {
- const _NonNullHostApiCodec();
+class _NullableReturnHostApiCodec extends StandardMessageCodec {
+ const _NullableReturnHostApiCodec();
}
-class NonNullHostApi {
- /// Constructor for [NonNullHostApi]. The [binaryMessenger] named argument is
+class NullableReturnHostApi {
+ /// Constructor for [NullableReturnHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
- NonNullHostApi({BinaryMessenger? binaryMessenger})
+ NullableReturnHostApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;
- static const MessageCodec<Object?> codec = _NonNullHostApiCodec();
+ static const MessageCodec<Object?> codec = _NullableReturnHostApiCodec();
Future<int?> doit() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.NonNullHostApi.doit', codec,
+ 'dev.flutter.pigeon.NullableReturnHostApi.doit', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
await channel.send(null) as Map<Object?, Object?>?;
@@ -52,19 +52,19 @@
}
}
-class _NonNullFlutterApiCodec extends StandardMessageCodec {
- const _NonNullFlutterApiCodec();
+class _NullableReturnFlutterApiCodec extends StandardMessageCodec {
+ const _NullableReturnFlutterApiCodec();
}
-abstract class NonNullFlutterApi {
- static const MessageCodec<Object?> codec = _NonNullFlutterApiCodec();
+abstract class NullableReturnFlutterApi {
+ static const MessageCodec<Object?> codec = _NullableReturnFlutterApiCodec();
int? doit();
- static void setup(NonNullFlutterApi? api,
+ static void setup(NullableReturnFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.NonNullFlutterApi.doit', codec,
+ 'dev.flutter.pigeon.NullableReturnFlutterApi.doit', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
channel.setMessageHandler(null);
@@ -153,3 +153,150 @@
}
}
}
+
+class _NullableCollectionReturnHostApiCodec extends StandardMessageCodec {
+ const _NullableCollectionReturnHostApiCodec();
+}
+
+class NullableCollectionReturnHostApi {
+ /// Constructor for [NullableCollectionReturnHostApi]. The [binaryMessenger] named argument is
+ /// available for dependency injection. If it is left null, the default
+ /// BinaryMessenger will be used which routes to the host platform.
+ NullableCollectionReturnHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec =
+ _NullableCollectionReturnHostApiCodec();
+
+ Future<List<String?>?> doit() async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableCollectionReturnHostApi.doit', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(null) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as List<Object?>?)?.cast<String?>();
+ }
+ }
+}
+
+class _NullableCollectionReturnFlutterApiCodec extends StandardMessageCodec {
+ const _NullableCollectionReturnFlutterApiCodec();
+}
+
+abstract class NullableCollectionReturnFlutterApi {
+ static const MessageCodec<Object?> codec =
+ _NullableCollectionReturnFlutterApiCodec();
+
+ List<String?>? doit();
+ static void setup(NullableCollectionReturnFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableCollectionReturnFlutterApi.doit', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ // ignore message
+ final List<String?>? output = api.doit();
+ return output;
+ });
+ }
+ }
+ }
+}
+
+class _NullableCollectionArgHostApiCodec extends StandardMessageCodec {
+ const _NullableCollectionArgHostApiCodec();
+}
+
+class NullableCollectionArgHostApi {
+ /// Constructor for [NullableCollectionArgHostApi]. The [binaryMessenger] named argument is
+ /// available for dependency injection. If it is left null, the default
+ /// BinaryMessenger will be used which routes to the host platform.
+ NullableCollectionArgHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec =
+ _NullableCollectionArgHostApiCodec();
+
+ Future<List<String?>> doit(List<String?>? arg_x) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableCollectionArgHostApi.doit', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_x]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else if (replyMap['result'] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyMap['result'] as List<Object?>?)!.cast<String?>();
+ }
+ }
+}
+
+class _NullableCollectionArgFlutterApiCodec extends StandardMessageCodec {
+ const _NullableCollectionArgFlutterApiCodec();
+}
+
+abstract class NullableCollectionArgFlutterApi {
+ static const MessageCodec<Object?> codec =
+ _NullableCollectionArgFlutterApiCodec();
+
+ List<String?> doit(List<String?>? x);
+ static void setup(NullableCollectionArgFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableCollectionArgFlutterApi.doit', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.NullableCollectionArgFlutterApi.doit was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final List<String?>? arg_x =
+ (args[0] as List<Object?>?)?.cast<String?>();
+ final List<String?> output = api.doit(arg_x);
+ return output;
+ });
+ }
+ }
+ }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
index 9b21234..dad6b7c 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/pubspec.yaml
@@ -10,7 +10,7 @@
sdk: flutter
dev_dependencies:
- build_runner: ^1.11.0
+ build_runner: ^2.1.10
flutter_test:
sdk: flutter
mockito: ^5.0.4
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
index 9c0ab20..5cd468c 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
@@ -13,7 +13,13 @@
import 'null_safe_test.mocks.dart';
import 'test_util.dart';
-@GenerateMocks(<Type>[BinaryMessenger, NullableArgFlutterApi])
+@GenerateMocks(<Type>[
+ BinaryMessenger,
+ NullableArgFlutterApi,
+ NullableReturnFlutterApi,
+ NullableCollectionArgFlutterApi,
+ NullableCollectionReturnFlutterApi,
+])
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -105,6 +111,21 @@
expect(await api.doit(null), 123);
});
+ test('send null collection parameter', () async {
+ final BinaryMessenger mockMessenger = MockBinaryMessenger();
+ const String channel =
+ 'dev.flutter.pigeon.NullableCollectionArgHostApi.doit';
+ when(mockMessenger.send(channel, any))
+ .thenAnswer((Invocation realInvocation) async {
+ return Api.codec.encodeMessage(<String?, Object?>{
+ 'result': <String?>['123']
+ });
+ });
+ final NullableCollectionArgHostApi api =
+ NullableCollectionArgHostApi(binaryMessenger: mockMessenger);
+ expect(await api.doit(null), <String?>['123']);
+ });
+
test('receive null parameters', () {
final MockNullableArgFlutterApi mockFlutterApi =
MockNullableArgFlutterApi();
@@ -115,8 +136,9 @@
final Completer<int> resultCompleter = Completer<int>();
// Null check operator is used because ServicesBinding.instance is nullable
// in earlier versions of Flutter.
- // ignore: unnecessary_non_null_assertion
- ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
+ _ambiguate(ServicesBinding.instance)!
+ .defaultBinaryMessenger
+ .handlePlatformMessage(
'dev.flutter.pigeon.NullableArgFlutterApi.doit',
NullableArgFlutterApi.codec.encodeMessage(<Object?>[null]),
(ByteData? data) {
@@ -131,4 +153,121 @@
// Removes message handlers from global default binary messenger.
NullableArgFlutterApi.setup(null);
});
+
+ test('receive null collection parameters', () {
+ final MockNullableCollectionArgFlutterApi mockFlutterApi =
+ MockNullableCollectionArgFlutterApi();
+ when(mockFlutterApi.doit(null)).thenReturn(<String?>['14']);
+
+ NullableCollectionArgFlutterApi.setup(mockFlutterApi);
+
+ final Completer<List<String?>> resultCompleter = Completer<List<String?>>();
+ // Null check operator is used because ServicesBinding.instance is nullable
+ // in earlier versions of Flutter.
+ _ambiguate(ServicesBinding.instance)!
+ .defaultBinaryMessenger
+ .handlePlatformMessage(
+ 'dev.flutter.pigeon.NullableCollectionArgFlutterApi.doit',
+ NullableCollectionArgFlutterApi.codec.encodeMessage(<Object?>[null]),
+ (ByteData? data) {
+ resultCompleter.complete(
+ (NullableCollectionArgFlutterApi.codec.decodeMessage(data)!
+ as List<Object?>)
+ .cast<String>(),
+ );
+ },
+ );
+
+ expect(resultCompleter.future, completion(<String>['14']));
+
+ // Removes message handlers from global default binary messenger.
+ NullableArgFlutterApi.setup(null);
+ });
+
+ test('receive null return', () async {
+ final BinaryMessenger mockMessenger = MockBinaryMessenger();
+ const String channel = 'dev.flutter.pigeon.NullableReturnHostApi.doit';
+ when(mockMessenger.send(channel, any))
+ .thenAnswer((Invocation realInvocation) async {
+ return NullableReturnHostApi.codec
+ .encodeMessage(<String?, Object?>{'result': null});
+ });
+ final NullableReturnHostApi api =
+ NullableReturnHostApi(binaryMessenger: mockMessenger);
+ expect(await api.doit(), null);
+ });
+
+ test('receive null collection return', () async {
+ final BinaryMessenger mockMessenger = MockBinaryMessenger();
+ const String channel =
+ 'dev.flutter.pigeon.NullableCollectionReturnHostApi.doit';
+ when(mockMessenger.send(channel, any))
+ .thenAnswer((Invocation realInvocation) async {
+ return NullableCollectionReturnHostApi.codec
+ .encodeMessage(<String?, Object?>{'result': null});
+ });
+ final NullableCollectionReturnHostApi api =
+ NullableCollectionReturnHostApi(binaryMessenger: mockMessenger);
+ expect(await api.doit(), null);
+ });
+
+ test('send null return', () async {
+ final MockNullableReturnFlutterApi mockFlutterApi =
+ MockNullableReturnFlutterApi();
+ when(mockFlutterApi.doit()).thenReturn(null);
+
+ NullableReturnFlutterApi.setup(mockFlutterApi);
+
+ final Completer<int?> resultCompleter = Completer<int?>();
+ // Null check operator is used because ServicesBinding.instance is nullable
+ // in earlier versions of Flutter.
+ _ambiguate(ServicesBinding.instance)!
+ .defaultBinaryMessenger
+ .handlePlatformMessage(
+ 'dev.flutter.pigeon.NullableReturnFlutterApi.doit',
+ NullableReturnFlutterApi.codec.encodeMessage(<Object?>[]),
+ (ByteData? data) {
+ resultCompleter.complete(null);
+ },
+ );
+
+ expect(resultCompleter.future, completion(null));
+
+ // Removes message handlers from global default binary messenger.
+ NullableArgFlutterApi.setup(null);
+ });
+
+ test('send null collection return', () async {
+ final MockNullableCollectionReturnFlutterApi mockFlutterApi =
+ MockNullableCollectionReturnFlutterApi();
+ when(mockFlutterApi.doit()).thenReturn(null);
+
+ NullableCollectionReturnFlutterApi.setup(mockFlutterApi);
+
+ final Completer<List<String?>?> resultCompleter =
+ Completer<List<String?>?>();
+ // Null check operator is used because ServicesBinding.instance is nullable
+ // in earlier versions of Flutter.
+ _ambiguate(ServicesBinding.instance)!
+ .defaultBinaryMessenger
+ .handlePlatformMessage(
+ 'dev.flutter.pigeon.NullableCollectionReturnFlutterApi.doit',
+ NullableCollectionReturnFlutterApi.codec.encodeMessage(<Object?>[]),
+ (ByteData? data) {
+ resultCompleter.complete(null);
+ },
+ );
+
+ expect(resultCompleter.future, completion(null));
+
+ // Removes message handlers from global default binary messenger.
+ NullableArgFlutterApi.setup(null);
+ });
}
+
+/// This allows a value of type T or T? to be treated as a value of type T?.
+///
+/// We use this so that APIs that have become non-nullable can still be used
+/// with `!` and `?` on the stable branch.
+// TODO(stuartmorgan): Remove this once 2.13 or later is on the stable channel.
+T? _ambiguate<T>(T? value) => value;
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart
index 5f8f129..9b5bfcb 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.mocks.dart
@@ -56,3 +56,38 @@
(super.noSuchMethod(Invocation.method(#doit, [x]), returnValue: 0)
as int);
}
+
+/// A class which mocks [NullableReturnFlutterApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockNullableReturnFlutterApi extends _i1.Mock
+ implements _i6.NullableReturnFlutterApi {
+ MockNullableReturnFlutterApi() {
+ _i1.throwOnMissingStub(this);
+ }
+}
+
+/// A class which mocks [NullableCollectionArgFlutterApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockNullableCollectionArgFlutterApi extends _i1.Mock
+ implements _i6.NullableCollectionArgFlutterApi {
+ MockNullableCollectionArgFlutterApi() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ List<String?> doit(List<String?>? x) => (super
+ .noSuchMethod(Invocation.method(#doit, [x]), returnValue: <String?>[])
+ as List<String?>);
+}
+
+/// A class which mocks [NullableCollectionReturnFlutterApi].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockNullableCollectionReturnFlutterApi extends _i1.Mock
+ implements _i6.NullableCollectionReturnFlutterApi {
+ MockNullableCollectionReturnFlutterApi() {
+ _i1.throwOnMissingStub(this);
+ }
+}
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index bd17ad5..a08046a 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 3.0.1 # This must match the version in lib/generator_tools.dart
+version: 3.0.2 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 4f03de6..a7c2bef 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -204,6 +204,50 @@
);
});
+ test('nested non-nullable class', () {
+ final Root root = Root(apis: <Api>[], classes: <Class>[
+ Class(
+ name: 'Input',
+ fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'String',
+ isNullable: false,
+ ),
+ name: 'input',
+ offset: null)
+ ],
+ ),
+ Class(
+ name: 'Nested',
+ fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'Input',
+ isNullable: false,
+ ),
+ name: 'nested',
+ offset: null)
+ ],
+ )
+ ], enums: <Enum>[]);
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(), root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ contains(
+ 'pigeonMap[\'nested\'] = nested.encode()',
+ ),
+ );
+ expect(
+ code.replaceAll('\n', ' ').replaceAll(' ', ''),
+ contains(
+ 'nested: Input.decode(pigeonMap[\'nested\']!)',
+ ),
+ );
+ });
+
test('flutterapi', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
@@ -401,6 +445,51 @@
expect(code, contains('EnumClass doSomething(EnumClass arg0);'));
});
+ test('flutter non-nullable enum argument with enum class', () {
+ final Root root = Root(apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doSomething',
+ arguments: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'EnumClass',
+ isNullable: false,
+ ),
+ name: '',
+ offset: null)
+ ],
+ returnType:
+ const TypeDeclaration(baseName: 'EnumClass', isNullable: false),
+ isAsynchronous: false,
+ )
+ ])
+ ], classes: <Class>[
+ Class(name: 'EnumClass', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(
+ baseName: 'Enum',
+ isNullable: false,
+ ),
+ name: 'enum1',
+ offset: null)
+ ]),
+ ], enums: <Enum>[
+ Enum(
+ name: 'Enum',
+ members: <String>[
+ 'one',
+ 'two',
+ ],
+ )
+ ]);
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('pigeonMap[\'enum1\'] = enum1.index;'));
+ expect(code, contains('enum1: Enum.values[pigeonMap[\'enum1\']! as int]'));
+ });
+
test('host void argument', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
@@ -893,6 +982,34 @@
expect(code, contains('return (replyMap[\'result\'] as int?);'));
});
+ test('return nullable collection host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'List',
+ isNullable: true,
+ typeArguments: <TypeDeclaration>[
+ TypeDeclaration(baseName: 'int', isNullable: true)
+ ]),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('Future<List<int?>?> doit()'));
+ expect(
+ code,
+ contains(
+ 'return (replyMap[\'result\'] as List<Object?>?)?.cast<int?>();'));
+ });
+
test('return nullable async host', () {
final Root root = Root(
apis: <Api>[