[pigeon] implements nullable arguments (#895)
* [pigeon] implemented nullable arguements
* implemented dart
* implemented java
* objc implementation
* added generated unit tests
* fixed some objc tests
* added a dart platform test
* added ios platform tests
* added platform test for android
* fixed rebase
* updated pubspec
* stuart feedback
* updated readme
* updated nullability of async nonnull parameters for objc
* updated version to 2.0.0
* stuart feedback
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index a8c1255..31676bd 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.0.0
+
+* Implements nullable parameters.
+* **BREAKING CHANGE** - Nonnull parameters to async methods on HostApis for ObjC
+ now have the proper nullability hints.
+
## 1.0.19
* Implements nullable return types.
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index b74f4fe..e773591 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -64,8 +64,6 @@
`void`.
1) Generics are supported, but can currently only be used with nullable types
(example: `List<int?>`).
-1) Arguments must be non-nullable. Fields on classes and return types can be
- nullable or non-nullable.
## Supported Datatypes
@@ -122,13 +120,13 @@
Pigeon supports generating null-safe code, but it doesn't yet support:
-1) Nullable method parameters
1) Nullable generics type arguments
It does support:
1) Nullable and Non-nullable class fields.
1) Nullable return values
+1) Nullable method parameters
The default is to generate null-safe code but in order to generate non-null-safe
code run Pigeon with the extra argument `--no-dart_null_safety`. For example:
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 2f735b4..95ecbcb 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -134,7 +134,7 @@
return func.arguments.isEmpty
? ''
: indexMap(func.arguments, (int index, NamedType arg) {
- final String type = _addGenericTypes(arg.type, nullTag);
+ final String type = _addGenericTypesNullable(arg.type, nullTag);
final String argName = getArgumentName(index, arg);
return '$type $argName';
}).join(', ');
@@ -182,7 +182,7 @@
String argNameFunc(int index, NamedType type) =>
_getSafeArgumentName(index, type);
final Iterable<String> argNames = indexMap(func.arguments, argNameFunc);
- sendArgument = '<Object>[${argNames.join(', ')}]';
+ sendArgument = '<Object?>[${argNames.join(', ')}]';
argSignature = _getMethodArgumentsSignature(func, argNameFunc, nullTag);
}
indent.write(
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 9a7487a..948c197 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -8,7 +8,7 @@
import 'ast.dart';
/// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '1.0.19';
+const String pigeonVersion = '2.0.0';
/// Read all the content from [stdin] to a String.
String readStdin() {
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index ef17076..9e97488 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -127,8 +127,8 @@
: _nullsafeJavaTypeForDartType(method.returnType);
final List<String> argSignature = <String>[];
if (method.arguments.isNotEmpty) {
- final Iterable<String> argTypes =
- method.arguments.map((NamedType e) => _javaTypeForDartType(e.type));
+ final Iterable<String> argTypes = method.arguments
+ .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type));
final Iterable<String> argNames =
method.arguments.map((NamedType e) => e.name);
argSignature
@@ -180,16 +180,20 @@
final bool isInt = arg.type.baseName == 'int';
final String argType =
isInt ? 'Number' : _javaTypeForDartType(arg.type);
- final String argCast = isInt ? '.longValue()' : '';
final String argName = _getSafeArgumentName(index, arg);
+ final String argExpression = isInt
+ ? '($argName == null) ? null : $argName.longValue()'
+ : argName;
indent
.writeln('$argType $argName = ($argType)args.get($index);');
- indent.write('if ($argName == null) ');
- indent.scoped('{', '}', () {
- indent.writeln(
- 'throw new NullPointerException("$argName unexpectedly null.");');
- });
- methodArgument.add('$argName$argCast');
+ if (!arg.type.isNullable) {
+ indent.write('if ($argName == null) ');
+ indent.scoped('{', '}', () {
+ indent.writeln(
+ 'throw new NullPointerException("$argName unexpectedly null.");');
+ });
+ }
+ methodArgument.add(argExpression);
});
}
if (method.isAsynchronous) {
@@ -311,8 +315,8 @@
indent.write('public void ${func.name}(Reply<$returnType> callback) ');
sendArgument = 'null';
} else {
- final Iterable<String> argTypes =
- func.arguments.map((NamedType e) => _javaTypeForDartType(e.type));
+ final Iterable<String> argTypes = func.arguments
+ .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type));
final Iterable<String> argNames =
indexMap(func.arguments, _getSafeArgumentName);
sendArgument =
@@ -404,8 +408,9 @@
}
String _nullsafeJavaTypeForDartType(TypeDeclaration type) {
- final String nullSafe = type.isNullable ? '@Nullable' : '@NonNull';
- return '$nullSafe ${_javaTypeForDartType(type)}';
+ final String nullSafe =
+ type.isVoid ? '' : (type.isNullable ? '@Nullable ' : '@NonNull ');
+ return '$nullSafe${_javaTypeForDartType(type)}';
}
/// Casts variable named [varName] to the correct host datatype for [field].
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index 87a8d23..7839b38 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -375,7 +375,7 @@
_getSelectorComponents(func, lastArgName);
final Iterable<String> argTypes = followedByOne(
func.arguments.map((NamedType arg) {
- final String nullable = func.isAsynchronous ? 'nullable ' : '';
+ final String nullable = arg.type.isNullable ? 'nullable ' : '';
final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type);
return '$nullable${argType.ptr.trim()}';
}),
@@ -427,7 +427,9 @@
lastArgType = 'FlutterError *_Nullable *_Nonnull';
lastArgName = 'error';
}
- if (!func.returnType.isNullable) {
+ if (!func.returnType.isNullable &&
+ !func.returnType.isVoid &&
+ !func.isAsynchronous) {
indent.writeln('/// @return `nil` only when `error != nil`.');
}
indent.writeln(_makeObjcSignature(
@@ -605,7 +607,7 @@
map3(wholeNumbers.take(func.arguments.length), argNames, func.arguments,
(int count, String argName, NamedType arg) {
final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type);
- return '${argType.ptr}$argName = args[$count];';
+ return '${argType.ptr}$argName = GetNullableObjectAtIndex(args, $count);';
}).forEach(indent.writeln);
}
@@ -747,7 +749,9 @@
if (func.arguments.isEmpty) {
sendArgument = 'nil';
} else {
- sendArgument = '@[${argNames.join(', ')}]';
+ String makeVarOrNSNullExpression(String x) =>
+ '($x == nil) ? [NSNull null] : $x';
+ sendArgument = '@[${argNames.map(makeVarOrNSNullExpression).join(', ')}]';
}
indent.write(_makeObjcSignature(
func: func,
@@ -837,6 +841,10 @@
\tid result = dict[key];
\treturn (result == [NSNull null]) ? nil : result;
}
+static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) {
+\tid result = array[key];
+\treturn (result == [NSNull null]) ? nil : result;
+}
''');
}
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index 71082dd..a53a637 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -489,15 +489,6 @@
'Enums aren\'t yet supported for primitive return types: "${method.returnType}" in API: "${api.name}" method: "${method.name}" (https://github.com/flutter/flutter/issues/87307)',
));
}
- if (method.arguments.isNotEmpty &&
- method.arguments
- .any((NamedType element) => element.type.isNullable)) {
- result.add(Error(
- message:
- 'Nullable argument types aren\'t supported for Pigeon in API: "${api.name}" method: "${method.name}"',
- lineNumber: _calculateLineNumberNullable(source, method.offset),
- ));
- }
for (final NamedType unnamedType in method.arguments
.where((NamedType element) => element.type.baseName.isEmpty)) {
result.add(Error(
diff --git a/packages/pigeon/pigeons/nullable_returns.dart b/packages/pigeon/pigeons/nullable_returns.dart
index caa33ef..150d5d3 100644
--- a/packages/pigeon/pigeons/nullable_returns.dart
+++ b/packages/pigeon/pigeons/nullable_returns.dart
@@ -16,3 +16,13 @@
abstract class NonNullFlutterApi {
int? doit();
}
+
+@HostApi()
+abstract class NullableArgHostApi {
+ int doit(int? x);
+}
+
+@FlutterApi()
+abstract class NullableArgFlutterApi {
+ int doit(int? x);
+}
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs b/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
index 51db0ab..6154443 100644
--- a/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
@@ -5,7 +5,7 @@
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
-java.home=/Library/Java/JavaVirtualMachines/jdk-11-latest/Contents/Home
+java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
jvm.arguments=
offline.mode=false
override.workspace.settings=true
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java
new file mode 100644
index 0000000..1f79171
--- /dev/null
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java
@@ -0,0 +1,78 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package com.example.android_unit_tests;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MessageCodec;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Map;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class NullableReturnsTest {
+ @Test
+ public void nullArgHostApi() {
+ NullableReturns.NullableArgHostApi mockApi = mock(NullableReturns.NullableArgHostApi.class);
+ BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
+ NullableReturns.NullableArgHostApi.setup(binaryMessenger, mockApi);
+ ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> handler =
+ ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
+ verify(binaryMessenger).setMessageHandler(anyString(), handler.capture());
+ MessageCodec<Object> codec = NullableReturns.NullableArgHostApi.getCodec();
+ ByteBuffer message =
+ codec.encodeMessage(
+ new ArrayList<Object>() {
+ {
+ add(null);
+ }
+ });
+ message.rewind();
+ handler
+ .getValue()
+ .onMessage(
+ message,
+ (bytes) -> {
+ bytes.rewind();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> wrapped = (Map<String, Object>) codec.decodeMessage(bytes);
+ assertTrue(wrapped.containsKey("result"));
+ });
+ }
+
+ @Test
+ public void nullArgFlutterApi() {
+ BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
+ doAnswer(
+ invocation -> {
+ ByteBuffer message = invocation.getArgument(1);
+ BinaryMessenger.BinaryReply reply = invocation.getArgument(2);
+ message.position(0);
+ ArrayList<Object> args =
+ (ArrayList<Object>)
+ NullableReturns.NullableArgFlutterApi.getCodec().decodeMessage(message);
+ assertNull(args.get(0));
+ ByteBuffer replyData =
+ NullableReturns.NullableArgFlutterApi.getCodec().encodeMessage(args.get(0));
+ reply.reply(replyData);
+ return null;
+ })
+ .when(binaryMessenger)
+ .send(anyString(), any(), any());
+ NullableReturns.NullableArgFlutterApi api =
+ new NullableReturns.NullableArgFlutterApi(binaryMessenger);
+ boolean[] didCall = {false};
+ api.doit(
+ null,
+ (Long result) -> {
+ didCall[0] = true;
+ assertNull(result);
+ });
+ assertTrue(didCall[0]);
+ }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
index f318dc5..ae7444f 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
@@ -154,7 +154,7 @@
'dev.flutter.pigeon.HostEverything.echo', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_everything]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_everything]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
index 8c80603..e5926f0 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
@@ -32,7 +32,7 @@
'dev.flutter.pigeon.MultipleArityHostApi.subtract', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_x, arg_y]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_x, arg_y]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
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 97ea6a4..a06d692 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
@@ -108,7 +108,7 @@
'dev.flutter.pigeon.NonNullHostApi.search', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_nested]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_nested]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
index 5cce608..994ac01 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
@@ -157,7 +157,7 @@
'dev.flutter.pigeon.Api.search', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_request]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_request]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -186,7 +186,7 @@
'dev.flutter.pigeon.Api.doSearches', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_request]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_request]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -215,7 +215,7 @@
'dev.flutter.pigeon.Api.echo', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_requests]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_requests]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -244,7 +244,7 @@
'dev.flutter.pigeon.Api.anInt', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
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 f80c7d3..3766d06 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
@@ -78,3 +78,80 @@
}
}
}
+
+class _NullableArgHostApiCodec extends StandardMessageCodec {
+ const _NullableArgHostApiCodec();
+}
+
+class NullableArgHostApi {
+ /// Constructor for [NullableArgHostApi]. 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.
+ NullableArgHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = _NullableArgHostApiCodec();
+
+ Future<int> doit(int? arg_x) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableArgHostApi.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 int?)!;
+ }
+ }
+}
+
+class _NullableArgFlutterApiCodec extends StandardMessageCodec {
+ const _NullableArgFlutterApiCodec();
+}
+
+abstract class NullableArgFlutterApi {
+ static const MessageCodec<Object?> codec = _NullableArgFlutterApiCodec();
+
+ int doit(int? x);
+ static void setup(NullableArgFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullableArgFlutterApi.doit', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_x = (args[0] as int?);
+ assert(arg_x != null,
+ 'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null, expected non-null int.');
+ final int output = api.doit(arg_x!);
+ return output;
+ });
+ }
+ }
+ }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
index b71adc3..bd94b3c 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
@@ -32,7 +32,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.anInt', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -61,7 +61,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aBool', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -90,7 +90,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aString', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -119,7 +119,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aDouble', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -148,7 +148,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aMap', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -177,7 +177,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aList', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -206,7 +206,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.anInt32List', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -235,7 +235,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aBoolList', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
@@ -264,7 +264,7 @@
'dev.flutter.pigeon.PrimitiveHostApi.aStringIntMap', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
- await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+ await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
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 e412b78..ca4479e 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
@@ -7,6 +7,7 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_unit_tests/null_safe_pigeon.dart';
+import 'package:flutter_unit_tests/nullable_returns.gen.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'null_safe_test.mocks.dart';
@@ -89,4 +90,16 @@
expect(() async => api.anInt(1),
throwsA(const TypeMatcher<PlatformException>()));
});
+
+ test('send null parameter', () async {
+ final BinaryMessenger mockMessenger = MockBinaryMessenger();
+ const String channel = 'dev.flutter.pigeon.NullableArgHostApi.doit';
+ when(mockMessenger.send(channel, any))
+ .thenAnswer((Invocation realInvocation) async {
+ return Api.codec.encodeMessage(<String?, Object?>{'result': 123});
+ });
+ final NullableArgHostApi api =
+ NullableArgHostApi(binaryMessenger: mockMessenger);
+ expect(await api.doit(null), 123);
+ });
}
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
index ed58c5c..aea750f 100644
--- a/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
@@ -3,11 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
0D02163D27BC7B48009BD76F /* nullable_returns.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */; };
+ 0D36469D27C6BE3C0069B7BF /* NullableReturnsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */; };
+ 0D3646A027C6DCEC0069B7BF /* MockBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */; };
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D50127423FF75B100CD5B95 /* RunnerTests.m */; };
0D6FD3C526A76D400046D8BD /* primitive.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FD3C426A76D400046D8BD /* primitive.gen.m */; };
0D6FD3C726A777C00046D8BD /* PrimitiveTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FD3C626A777C00046D8BD /* PrimitiveTest.m */; };
@@ -67,6 +69,9 @@
/* Begin PBXFileReference section */
0D02163B27BC7B48009BD76F /* nullable_returns.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nullable_returns.gen.h; sourceTree = "<group>"; };
0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nullable_returns.gen.m; sourceTree = "<group>"; };
+ 0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NullableReturnsTest.m; sourceTree = "<group>"; };
+ 0D36469E27C6DCEC0069B7BF /* MockBinaryMessenger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockBinaryMessenger.h; sourceTree = "<group>"; };
+ 0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockBinaryMessenger.m; sourceTree = "<group>"; };
0D50127223FF75B100CD5B95 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0D50127423FF75B100CD5B95 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = "<group>"; };
0D50127623FF75B100CD5B95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -146,6 +151,7 @@
0D50127323FF75B100CD5B95 /* RunnerTests */ = {
isa = PBXGroup;
children = (
+ 0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */,
0D50127423FF75B100CD5B95 /* RunnerTests.m */,
0D50127623FF75B100CD5B95 /* Info.plist */,
0D8C35EA25D45A7900B76435 /* AsyncHandlersTest.m */,
@@ -159,6 +165,8 @@
0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */,
0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */,
0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */,
+ 0D36469E27C6DCEC0069B7BF /* MockBinaryMessenger.h */,
+ 0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@@ -388,8 +396,10 @@
0DF4E5C5266ECF4A00AEA855 /* AllDatatypesTest.m in Sources */,
0DF4E5C8266ED80900AEA855 /* EchoMessenger.m in Sources */,
0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */,
+ 0D36469D27C6BE3C0069B7BF /* NullableReturnsTest.m in Sources */,
0DF4E5CB266FDAE300AEA855 /* EnumTest.m in Sources */,
0D8C35EB25D45A7900B76435 /* AsyncHandlersTest.m in Sources */,
+ 0D3646A027C6DCEC0069B7BF /* MockBinaryMessenger.m in Sources */,
0D6FD3C726A777C00046D8BD /* PrimitiveTest.m in Sources */,
0D7A910A268D4A050056B5E1 /* ListTest.m in Sources */,
0DA5DFDB26CC3B3700D2354B /* HandlerBinaryMessenger.m in Sources */,
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
index 398aeb3..90d3fdd 100644
--- a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
@@ -4,6 +4,7 @@
#import <Flutter/Flutter.h>
#import <XCTest/XCTest.h>
+#import "MockBinaryMessenger.h"
#import "async_handlers.gen.h"
///////////////////////////////////////////////////////////////////////////////////////////
@@ -13,51 +14,6 @@
@end
///////////////////////////////////////////////////////////////////////////////////////////
-@interface MockBinaryMessenger : NSObject <FlutterBinaryMessenger>
-@property(nonatomic, copy) NSNumber *result;
-@property(nonatomic, retain) NSObject<FlutterMessageCodec> *codec;
-@property(nonatomic, retain) NSMutableDictionary<NSString *, FlutterBinaryMessageHandler> *handlers;
-- (instancetype)init NS_UNAVAILABLE;
-@end
-
-///////////////////////////////////////////////////////////////////////////////////////////
-@implementation MockBinaryMessenger
-
-- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec {
- self = [super init];
- if (self) {
- _codec = codec;
- _handlers = [[NSMutableDictionary alloc] init];
- }
- return self;
-}
-
-- (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection {
-}
-
-- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message {
-}
-
-- (void)sendOnChannel:(nonnull NSString *)channel
- message:(NSData *_Nullable)message
- binaryReply:(FlutterBinaryReply _Nullable)callback {
- if (self.result) {
- Value *output = [[Value alloc] init];
- output.number = self.result;
- callback([_codec encode:output]);
- }
-}
-
-- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
- binaryMessageHandler:
- (FlutterBinaryMessageHandler _Nullable)handler {
- _handlers[channel] = [handler copy];
- return _handlers.count;
-}
-
-@end
-
-///////////////////////////////////////////////////////////////////////////////////////////
@interface MockApi2Host : NSObject <Api2Host>
@property(nonatomic, copy) NSNumber *output;
@property(nonatomic, retain) FlutterError *voidVoidError;
@@ -66,7 +22,7 @@
///////////////////////////////////////////////////////////////////////////////////////////
@implementation MockApi2Host
-- (void)calculateValue:(Value *_Nullable)input
+- (void)calculateValue:(Value *)input
completion:(nonnull void (^)(Value *_Nullable, FlutterError *_Nullable))completion {
if (self.output) {
Value *output = [[Value alloc] init];
@@ -93,7 +49,7 @@
- (void)testAsyncHost2Flutter {
MockBinaryMessenger *binaryMessenger =
[[MockBinaryMessenger alloc] initWithCodec:Api2FlutterGetCodec()];
- binaryMessenger.result = @(2);
+ binaryMessenger.result = [Value makeWithNumber:@(2)];
Api2Flutter *api2Flutter = [[Api2Flutter alloc] initWithBinaryMessenger:binaryMessenger];
Value *input = [[Value alloc] init];
input.number = @(1);
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h
new file mode 100644
index 0000000..1f76103
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MockBinaryMessenger : NSObject <FlutterBinaryMessenger>
+@property(nonatomic, retain) NSObject *result;
+@property(nonatomic, retain) NSObject<FlutterMessageCodec> *codec;
+@property(nonatomic, retain) NSMutableDictionary<NSString *, FlutterBinaryMessageHandler> *handlers;
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec NS_DESIGNATED_INITIALIZER;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m
new file mode 100644
index 0000000..7b9e9fd
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m
@@ -0,0 +1,42 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "MockBinaryMessenger.h"
+
+@implementation MockBinaryMessenger
+
+- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec {
+ self = [super init];
+ if (self) {
+ _codec = codec;
+ _handlers = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection {
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message {
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel
+ message:(NSData *_Nullable)message
+ binaryReply:(FlutterBinaryReply _Nullable)callback {
+ if (self.result) {
+ callback([_codec encode:self.result]);
+ }
+}
+
+- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
+ binaryMessageHandler:
+ (FlutterBinaryMessageHandler _Nullable)handler {
+ _handlers[channel] = [handler copy];
+ return _handlers.count;
+}
+
+- (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
+}
+
+@end
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m
new file mode 100644
index 0000000..df344b8
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <XCTest/XCTest.h>
+#import "EchoMessenger.h"
+#import "MockBinaryMessenger.h"
+#import "nullable_returns.gen.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface MockNullableArgHostApi : NSObject <NRNullableArgHostApi>
+@property(nonatomic, assign) BOOL didCall;
+@property(nonatomic, copy) NSNumber* x;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation MockNullableArgHostApi
+- (nullable NSNumber*)doitX:(NSNumber* _Nullable)x
+ error:(FlutterError* _Nullable __autoreleasing* _Nonnull)error {
+ _didCall = YES;
+ self.x = x;
+ return x;
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NullableReturnsTest : XCTestCase
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation NullableReturnsTest
+
+- (void)testNullableParameterWithFlutterApi {
+ EchoBinaryMessenger* binaryMessenger =
+ [[EchoBinaryMessenger alloc] initWithCodec:NRNullableArgFlutterApiGetCodec()];
+ NRNullableArgFlutterApi* api =
+ [[NRNullableArgFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
+ XCTestExpectation* expectation = [self expectationWithDescription:@"callback"];
+ [api doitX:nil
+ completion:^(NSNumber* _Nonnull result, NSError* _Nullable error) {
+ XCTAssertNil(result);
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testNullableParameterWithHostApi {
+ MockNullableArgHostApi* api = [[MockNullableArgHostApi alloc] init];
+ MockBinaryMessenger* binaryMessenger =
+ [[MockBinaryMessenger alloc] initWithCodec:NRNullableArgHostApiGetCodec()];
+ NSString* channel = @"dev.flutter.pigeon.NullableArgHostApi.doit";
+ NRNullableArgHostApiSetup(binaryMessenger, api);
+ XCTAssertNotNil(binaryMessenger.handlers[channel]);
+ XCTestExpectation* expectation = [self expectationWithDescription:@"callback"];
+ NSData* arguments = [NRNullableArgHostApiGetCodec() encode:@[ [NSNull null] ]];
+ binaryMessenger.handlers[channel](arguments, ^(NSData* data) {
+ [expectation fulfill];
+ });
+ XCTAssertTrue(api.didCall);
+ XCTAssertNil(api.x);
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+@end
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 686205b..12f922e 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: 1.0.19 # This must match the version in lib/generator_tools.dart
+version: 2.0.0 # 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 647376b..00c8158 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -125,7 +125,7 @@
final String code = sink.toString();
expect(code, contains('class Api'));
expect(code, contains('Future<int> add(int arg_x, int arg_y)'));
- expect(code, contains('await channel.send(<Object>[arg_x, arg_y])'));
+ expect(code, contains('await channel.send(<Object?>[arg_x, arg_y])'));
});
test('flutter multiple args', () {
@@ -1064,4 +1064,56 @@
contains(
'Host platform returned null value for non-null return value.'));
});
+
+ test('nullable argument host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(isNullSafe: true), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('Future<void> doit(int? arg_foo) async {'));
+ });
+
+ test('nullable argument flutter', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(isNullSafe: true), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('void doit(int? foo);'));
+ });
}
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index 95f4652..57c4ef9 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -521,7 +521,9 @@
expect(code, contains('public interface Result<T> {'));
expect(code, contains('void error(Throwable error);'));
expect(
- code, contains('void doSomething(Input arg, Result<Output> result);'));
+ code,
+ contains(
+ 'void doSomething(@NonNull Input arg, Result<Output> result);'));
expect(code, contains('api.doSomething(argArg, resultCallback);'));
expect(code, contains('channel.setMessageHandler(null)'));
});
@@ -719,7 +721,7 @@
const JavaOptions javaOptions = JavaOptions(className: 'Messages');
generateJava(javaOptions, root, sink);
final String code = sink.toString();
- expect(code, contains('doit(List<Long> arg'));
+ expect(code, contains('doit(@NonNull List<Long> arg'));
});
test('flutter generics argument', () {
@@ -749,7 +751,7 @@
const JavaOptions javaOptions = JavaOptions(className: 'Messages');
generateJava(javaOptions, root, sink);
final String code = sink.toString();
- expect(code, contains('doit(List<Long> arg'));
+ expect(code, contains('doit(@NonNull List<Long> arg'));
});
test('host generics return', () {
@@ -829,13 +831,15 @@
generateJava(javaOptions, root, sink);
final String code = sink.toString();
expect(code, contains('class Messages'));
- expect(code, contains('Long add(Long x, Long y)'));
+ expect(code, contains('Long add(@NonNull Long x, @NonNull Long y)'));
expect(
code, contains('ArrayList<Object> args = (ArrayList<Object>)message;'));
expect(code, contains('Number xArg = (Number)args.get(0)'));
expect(code, contains('Number yArg = (Number)args.get(1)'));
- expect(code,
- contains('Long output = api.add(xArg.longValue(), yArg.longValue())'));
+ expect(
+ code,
+ contains(
+ 'Long output = api.add((xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue())'));
});
test('flutter multiple args', () {
@@ -868,7 +872,7 @@
expect(
code,
contains(
- 'public void add(Long xArg, Long yArg, Reply<Long> callback)'));
+ 'public void add(@NonNull Long xArg, @NonNull Long yArg, Reply<Long> callback)'));
expect(
code,
contains(
@@ -922,4 +926,61 @@
// Java doesn't accept nullability annotations in type arguments.
expect(code, contains('Result<Long>'));
});
+
+ test('nullable argument host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+ generateJava(javaOptions, root, sink);
+ final String code = sink.toString();
+ expect(code, contains(' void doit(@Nullable Long foo);'));
+ });
+
+ test('nullable argument flutter', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+ generateJava(javaOptions, root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ contains(
+ 'public void doit(@Nullable Long fooArg, Reply<Void> callback) {'));
+ });
}
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index c03738c..7044ecf 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -466,7 +466,7 @@
generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(code, contains('ABCInput fromMap'));
- expect(code, matches(r'ABCInput.*=.*args\[0\]'));
+ expect(code, matches(r'ABCInput.*=.*args.*0.*\;'));
expect(code, contains('void ABCApiSetup('));
});
@@ -848,7 +848,7 @@
name: 'foo',
type: const TypeDeclaration(
baseName: 'Map',
- isNullable: true,
+ isNullable: false,
typeArguments: <TypeDeclaration>[
TypeDeclaration(baseName: 'String', isNullable: true),
TypeDeclaration(baseName: 'Object', isNullable: true),
@@ -900,7 +900,7 @@
expect(
code,
contains(
- '(void)doSomethingInput:(nullable ABCInput *)input completion:(void(^)(FlutterError *_Nullable))completion'));
+ '(void)doSomethingInput:(ABCInput *)input completion:(void(^)(FlutterError *_Nullable))completion'));
});
test('async output(input) HostApi header', () {
@@ -942,7 +942,7 @@
expect(
code,
contains(
- '(void)doSomethingInput:(nullable ABCInput *)input completion:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
+ '(void)doSomethingInput:(ABCInput *)input completion:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
});
test('async output(void) HostApi header', () {
@@ -1220,7 +1220,10 @@
generateObjcSource(
const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
- expect(code, contains('NSArray<NSNumber *> *arg_arg = args[0]'));
+ expect(
+ code,
+ contains(
+ 'NSArray<NSNumber *> *arg_arg = GetNullableObjectAtIndex(args, 0)'));
}
});
@@ -1408,8 +1411,10 @@
const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(code, contains('NSArray *args = message;'));
- expect(code, contains('NSNumber *arg_x = args[0];'));
- expect(code, contains('NSNumber *arg_y = args[1];'));
+ expect(code,
+ contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);'));
+ expect(code,
+ contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);'));
expect(code,
contains('NSNumber *output = [api addX:arg_x y:arg_y error:&error]'));
}
@@ -1443,7 +1448,7 @@
expect(
code,
contains(
- '- (void)addX:(nullable NSNumber *)x y:(nullable NSNumber *)y completion:(void(^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;'));
+ '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void(^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;'));
}
{
final StringBuffer sink = StringBuffer();
@@ -1451,8 +1456,10 @@
const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
final String code = sink.toString();
expect(code, contains('NSArray *args = message;'));
- expect(code, contains('NSNumber *arg_x = args[0];'));
- expect(code, contains('NSNumber *arg_y = args[1];'));
+ expect(code,
+ contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);'));
+ expect(code,
+ contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);'));
expect(code, contains('[api addX:arg_x y:arg_y completion:'));
}
});
@@ -1496,7 +1503,10 @@
code,
contains(
'- (void)addX:(NSNumber *)arg_x y:(NSNumber *)arg_y completion:(void(^)(NSNumber *_Nullable, NSError *_Nullable))completion {'));
- expect(code, contains('[channel sendMessage:@[arg_x, arg_y] reply:'));
+ expect(
+ code,
+ contains(
+ '[channel sendMessage:@[(arg_x == nil) ? [NSNull null] : arg_x, (arg_y == nil) ? [NSNull null] : arg_y] reply:'));
}
});
@@ -1652,4 +1662,73 @@
final String code = sink.toString();
expect(code, matches(r'nullable NSNumber.*doitWithError'));
});
+
+ test('nullable argument host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ {
+ final StringBuffer sink = StringBuffer();
+ generateObjcHeader(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('doitFoo:(nullable NSNumber *)foo'));
+ }
+ {
+ final StringBuffer sink = StringBuffer();
+ generateObjcSource(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code,
+ contains('NSNumber *arg_foo = GetNullableObjectAtIndex(args, 0);'));
+ }
+ });
+
+ test('nullable argument flutter', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration.voidDeclaration(),
+ arguments: <NamedType>[
+ NamedType(
+ name: 'foo',
+ type: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ )),
+ ])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ {
+ final StringBuffer sink = StringBuffer();
+ generateObjcHeader(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('doitFoo:(nullable NSNumber *)foo'));
+ }
+ {
+ final StringBuffer sink = StringBuffer();
+ generateObjcSource(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('- (void)doitFoo:(nullable NSNumber *)arg_foo'));
+ }
+ });
}
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 1d21f46..9922bc5 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -564,23 +564,6 @@
expect(results.errors[0].message, contains('Constructor'));
});
- test('nullable api arguments', () {
- const String code = '''
-class Foo {
- int? x;
-}
-
-@HostApi()
-abstract class Api {
- Foo doit(Foo foo1, Foo? foo2);
-}
-''';
- final ParseResults results = _parseSource(code);
- expect(results.errors.length, 1);
- expect(results.errors[0].lineNumber, 7);
- expect(results.errors[0].message, contains('Nullable'));
- });
-
test('test invalid import', () {
const String code = 'import \'foo.dart\';\n';
final ParseResults results = _parseSource(code);
@@ -1045,4 +1028,17 @@
expect(results.errors.length, 0);
expect(results.root.apis[0].methods[0].returnType.isNullable, isTrue);
});
+
+ test('nullable parameters', () {
+ const String code = '''
+@HostApi()
+abstract class Api {
+ void calc(int? value);
+}
+''';
+ final ParseResults results = _parseSource(code);
+ expect(results.errors.length, 0);
+ expect(
+ results.root.apis[0].methods[0].arguments[0].type.isNullable, isTrue);
+ });
}