[pigeon] implements nullable return types (#849)
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 8c7d93a..a8c1255 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.19
+
+* Implements nullable return types.
+
## 1.0.18
* [front-end] Fix error caused by parsing `copyrightHeaders` passed to options in `@ConfigurePigeon`.
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index 9dfcec7..b74f4fe 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -64,8 +64,8 @@
`void`.
1) Generics are supported, but can currently only be used with nullable types
(example: `List<int?>`).
-1) Arguments and return values to methods must be non-nullable. Fields on
- classes can be nullable or non-nullable.
+1) Arguments must be non-nullable. Fields on classes and return types can be
+ nullable or non-nullable.
## Supported Datatypes
@@ -124,11 +124,11 @@
1) Nullable method parameters
1) Nullable generics type arguments
-1) Nullable return values
It does support:
1) Nullable and Non-nullable class fields.
+1) Nullable return values
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/bin/run_tests.dart b/packages/pigeon/bin/run_tests.dart
index 927c517..a506b78 100644
--- a/packages/pigeon/bin/run_tests.dart
+++ b/packages/pigeon/bin/run_tests.dart
@@ -144,6 +144,8 @@
'$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
'pigeons/non_null_fields.dart':
'$flutterUnitTestsPath/lib/non_null_fields.gen.dart',
+ 'pigeons/nullable_returns.dart':
+ '$flutterUnitTestsPath/lib/nullable_returns.gen.dart',
});
if (generateCode != 0) {
return generateCode;
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index fe40d43..2f735b4 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -186,7 +186,7 @@
argSignature = _getMethodArgumentsSignature(func, argNameFunc, nullTag);
}
indent.write(
- 'Future<${_addGenericTypes(func.returnType, nullTag)}> ${func.name}($argSignature) async ',
+ 'Future<${_addGenericTypesNullable(func.returnType, nullTag)}> ${func.name}($argSignature) async ',
);
indent.scoped('{', '}', () {
final String channelName = makeChannelName(api, func);
@@ -200,16 +200,18 @@
final String returnType =
_makeGenericTypeArguments(func.returnType, nullTag);
final String castCall = _makeGenericCastCall(func.returnType, nullTag);
+ const String accessor = 'replyMap[\'${Keys.result}\']';
+ final String unwrapper =
+ func.returnType.isNullable ? '' : unwrapOperator;
final String returnStatement = func.returnType.isVoid
? 'return;'
- : 'return (replyMap[\'${Keys.result}\'] as $returnType$nullTag)$unwrapOperator$castCall;';
+ : 'return ($accessor as $returnType$nullTag)$unwrapper$castCall;';
indent.format('''
final Map<Object$nullTag, Object$nullTag>$nullTag replyMap =\n\t\tawait channel.send($sendArgument) as Map<Object$nullTag, Object$nullTag>$nullTag;
if (replyMap == null) {
\tthrow PlatformException(
\t\tcode: 'channel-error',
\t\tmessage: 'Unable to establish connection on channel.',
-\t\tdetails: null,
\t);
} else if (replyMap['error'] != null) {
\tfinal Map<Object$nullTag, Object$nullTag> error = (replyMap['${Keys.error}'] as Map<Object$nullTag, Object$nullTag>$nullTag)$unwrapOperator;
@@ -217,7 +219,19 @@
\t\tcode: (error['${Keys.errorCode}'] as String$nullTag)$unwrapOperator,
\t\tmessage: error['${Keys.errorMessage}'] as String$nullTag,
\t\tdetails: error['${Keys.errorDetails}'],
-\t);
+\t);''');
+ // On iOS we can return nil from functions to accommodate error
+ // handling. Returning a nil value and not returning an error is an
+ // exception.
+ if (!func.returnType.isNullable && !func.returnType.isVoid) {
+ indent.format('''
+} else if (replyMap['${Keys.result}'] == null) {
+\tthrow PlatformException(
+\t\tcode: 'null-error',
+\t\tmessage: 'Host platform returned null value for non-null return value.',
+\t);''');
+ }
+ indent.format('''
} else {
\t$returnStatement
}''');
@@ -255,8 +269,8 @@
for (final Method func in api.methods) {
final bool isAsync = func.isAsynchronous;
final String returnType = isAsync
- ? 'Future<${_addGenericTypes(func.returnType, nullTag)}>'
- : _addGenericTypes(func.returnType, nullTag);
+ ? 'Future<${_addGenericTypesNullable(func.returnType, nullTag)}>'
+ : _addGenericTypesNullable(func.returnType, nullTag);
final String argSignature = _getMethodArgumentsSignature(
func,
_getArgumentName,
@@ -294,7 +308,7 @@
);
indent.scoped('{', '});', () {
final String returnType =
- _addGenericTypes(func.returnType, nullTag);
+ _addGenericTypesNullable(func.returnType, nullTag);
final bool isAsync = func.isAsynchronous;
final String emptyReturnStatement = isMockHandler
? 'return <Object$nullTag, Object$nullTag>{};'
@@ -387,9 +401,9 @@
}
}
-String _addGenericTypesNullable(NamedType field, String nullTag) {
- final String genericdType = _addGenericTypes(field.type, nullTag);
- return field.type.isNullable ? '$genericdType$nullTag' : genericdType;
+String _addGenericTypesNullable(TypeDeclaration type, String nullTag) {
+ final String genericdType = _addGenericTypes(type, nullTag);
+ return type.isNullable ? '$genericdType$nullTag' : genericdType;
}
/// Generates Dart source code for the given AST represented by [root],
@@ -495,7 +509,8 @@
'(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$castCallPrefix$castCall',
);
} else {
- final String genericdType = _addGenericTypesNullable(field, nullTag);
+ final String genericdType =
+ _addGenericTypesNullable(field.type, nullTag);
if (field.type.isNullable) {
indent.add(
'pigeonMap[\'${field.name}\'] as $genericdType',
@@ -532,7 +547,7 @@
writeConstructor();
indent.addln('');
for (final NamedType field in klass.fields) {
- final String datatype = _addGenericTypesNullable(field, nullTag);
+ final String datatype = _addGenericTypesNullable(field.type, nullTag);
indent.writeln('$datatype ${field.name};');
}
if (klass.fields.isNotEmpty) {
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 850e6b3..9a7487a 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.18';
+const String pigeonVersion = '1.0.19';
/// 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 dd9c391..ef17076 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -118,33 +118,134 @@
void _writeHostApi(Indent indent, Api api) {
assert(api.location == ApiLocation.host);
+ /// Write a method in the interface.
+ /// Example:
+ /// int add(int x, int y);
+ void writeInterfaceMethod(final Method method) {
+ final String returnType = method.isAsynchronous
+ ? 'void'
+ : _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> argNames =
+ method.arguments.map((NamedType e) => e.name);
+ argSignature
+ .addAll(map2(argTypes, argNames, (String argType, String argName) {
+ return '$argType $argName';
+ }));
+ }
+ if (method.isAsynchronous) {
+ final String resultType = method.returnType.isVoid
+ ? 'Void'
+ : _javaTypeForDartType(method.returnType);
+ argSignature.add('Result<$resultType> result');
+ }
+ indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
+ }
+
+ /// Write a static setup function in the interface.
+ /// Example:
+ /// static void setup(BinaryMessenger binaryMessenger, Foo api) {...}
+ void writeMethodSetup(final Method method) {
+ final String channelName = makeChannelName(api, method);
+ indent.write('');
+ indent.scoped('{', '}', () {
+ indent.writeln('BasicMessageChannel<Object> channel =');
+ indent.inc();
+ indent.inc();
+ indent.writeln(
+ 'new BasicMessageChannel<>(binaryMessenger, "$channelName", getCodec());');
+ indent.dec();
+ indent.dec();
+ indent.write('if (api != null) ');
+ indent.scoped('{', '} else {', () {
+ indent.write('channel.setMessageHandler((message, reply) -> ');
+ indent.scoped('{', '});', () {
+ final String returnType = method.returnType.isVoid
+ ? 'Void'
+ : _javaTypeForDartType(method.returnType);
+ indent.writeln('Map<String, Object> wrapped = new HashMap<>();');
+ indent.write('try ');
+ indent.scoped('{', '}', () {
+ final List<String> methodArgument = <String>[];
+ if (method.arguments.isNotEmpty) {
+ indent.writeln(
+ 'ArrayList<Object> args = (ArrayList<Object>)message;');
+ enumerate(method.arguments, (int index, NamedType arg) {
+ // The StandardMessageCodec can give us [Integer, Long] for
+ // a Dart 'int'. To keep things simple we just use 64bit
+ // longs in Pigeon with Java.
+ 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);
+ 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 (method.isAsynchronous) {
+ final String resultValue =
+ method.returnType.isVoid ? 'null' : 'result';
+ const String resultName = 'resultCallback';
+ indent.format('''
+Result<$returnType> $resultName = new Result<$returnType>() {
+\tpublic void success($returnType result) {
+\t\twrapped.put("${Keys.result}", $resultValue);
+\t\treply.reply(wrapped);
+\t}
+\tpublic void error(Throwable error) {
+\t\twrapped.put("${Keys.error}", wrapError(error));
+\t\treply.reply(wrapped);
+\t}
+};
+''');
+ methodArgument.add(resultName);
+ }
+ final String call =
+ 'api.${method.name}(${methodArgument.join(', ')})';
+ if (method.isAsynchronous) {
+ indent.writeln('$call;');
+ } else if (method.returnType.isVoid) {
+ indent.writeln('$call;');
+ indent.writeln('wrapped.put("${Keys.result}", null);');
+ } else {
+ indent.writeln('$returnType output = $call;');
+ indent.writeln('wrapped.put("${Keys.result}", output);');
+ }
+ });
+ indent.write('catch (Error | RuntimeException exception) ');
+ indent.scoped('{', '}', () {
+ indent
+ .writeln('wrapped.put("${Keys.error}", wrapError(exception));');
+ if (method.isAsynchronous) {
+ indent.writeln('reply.reply(wrapped);');
+ }
+ });
+ if (!method.isAsynchronous) {
+ indent.writeln('reply.reply(wrapped);');
+ }
+ });
+ });
+ indent.scoped(null, '}', () {
+ indent.writeln('channel.setMessageHandler(null);');
+ });
+ });
+ }
+
indent.writeln(
'/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/');
indent.write('public interface ${api.name} ');
indent.scoped('{', '}', () {
- for (final Method method in api.methods) {
- final String returnType = method.isAsynchronous
- ? 'void'
- : _javaTypeForDartType(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> argNames =
- method.arguments.map((NamedType e) => e.name);
- argSignature
- .addAll(map2(argTypes, argNames, (String argType, String argName) {
- return '$argType $argName';
- }));
- }
- if (method.isAsynchronous) {
- final String returnType = method.returnType.isVoid
- ? 'Void'
- : _javaTypeForDartType(method.returnType);
- argSignature.add('Result<$returnType> result');
- }
- indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
- }
+ api.methods.forEach(writeInterfaceMethod);
indent.addln('');
final String codecName = _getCodecName(api);
indent.format('''
@@ -158,98 +259,7 @@
indent.write(
'static void setup(BinaryMessenger binaryMessenger, ${api.name} api) ');
indent.scoped('{', '}', () {
- for (final Method method in api.methods) {
- final String channelName = makeChannelName(api, method);
- indent.write('');
- indent.scoped('{', '}', () {
- indent.writeln('BasicMessageChannel<Object> channel =');
- indent.inc();
- indent.inc();
- indent.writeln(
- 'new BasicMessageChannel<>(binaryMessenger, "$channelName", getCodec());');
- indent.dec();
- indent.dec();
- indent.write('if (api != null) ');
- indent.scoped('{', '} else {', () {
- indent.write('channel.setMessageHandler((message, reply) -> ');
- indent.scoped('{', '});', () {
- final String returnType = method.returnType.isVoid
- ? 'Void'
- : _javaTypeForDartType(method.returnType);
- indent.writeln('Map<String, Object> wrapped = new HashMap<>();');
- indent.write('try ');
- indent.scoped('{', '}', () {
- final List<String> methodArgument = <String>[];
- if (method.arguments.isNotEmpty) {
- indent.writeln(
- 'ArrayList<Object> args = (ArrayList<Object>)message;');
- enumerate(method.arguments, (int index, NamedType arg) {
- // The StandardMessageCodec can give us [Integer, Long] for
- // a Dart 'int'. To keep things simple we just use 64bit
- // longs in Pigeon with Java.
- 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);
- 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 (method.isAsynchronous) {
- final String resultValue =
- method.returnType.isVoid ? 'null' : 'result';
- const String resultName = 'resultCallback';
- indent.format('''
-Result<$returnType> $resultName = new Result<$returnType>() {
-\tpublic void success($returnType result) {
-\t\twrapped.put("${Keys.result}", $resultValue);
-\t\treply.reply(wrapped);
-\t}
-\tpublic void error(Throwable error) {
-\t\twrapped.put("${Keys.error}", wrapError(error));
-\t\treply.reply(wrapped);
-\t}
-};
-''');
- methodArgument.add(resultName);
- }
- final String call =
- 'api.${method.name}(${methodArgument.join(', ')})';
- if (method.isAsynchronous) {
- indent.writeln('$call;');
- } else if (method.returnType.isVoid) {
- indent.writeln('$call;');
- indent.writeln('wrapped.put("${Keys.result}", null);');
- } else {
- indent.writeln('$returnType output = $call;');
- indent.writeln('wrapped.put("${Keys.result}", output);');
- }
- });
- indent.write('catch (Error | RuntimeException exception) ');
- indent.scoped('{', '}', () {
- indent.writeln(
- 'wrapped.put("${Keys.error}", wrapError(exception));');
- if (method.isAsynchronous) {
- indent.writeln('reply.reply(wrapped);');
- }
- });
- if (!method.isAsynchronous) {
- indent.writeln('reply.reply(wrapped);');
- }
- });
- });
- indent.scoped(null, '}', () {
- indent.writeln('channel.setMessageHandler(null);');
- });
- });
- }
+ api.methods.forEach(writeMethodSetup);
});
});
}
@@ -393,6 +403,11 @@
return _javaTypeForBuiltinDartType(type) ?? type.baseName;
}
+String _nullsafeJavaTypeForDartType(TypeDeclaration type) {
+ final String nullSafe = type.isNullable ? '@Nullable' : '@NonNull';
+ return '$nullSafe ${_javaTypeForDartType(type)}';
+}
+
/// Casts variable named [varName] to the correct host datatype for [field].
/// This is for use in codecs where we may have a map representation of an
/// object.
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index 8ef3986..87a8d23 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -69,7 +69,7 @@
String _callbackForType(TypeDeclaration type, _ObjcPtr objcType) {
return type.isVoid
? 'void(^)(NSError *_Nullable)'
- : 'void(^)(${objcType.ptr.trim()}, NSError *_Nullable)';
+ : 'void(^)(${objcType.ptr.trim()}_Nullable, NSError *_Nullable)';
}
/// Represents an ObjC pointer (ex 'id', 'NSString *').
@@ -427,6 +427,9 @@
lastArgType = 'FlutterError *_Nullable *_Nonnull';
lastArgName = 'error';
}
+ if (!func.returnType.isNullable) {
+ indent.writeln('/// @return `nil` only when `error != nil`.');
+ }
indent.writeln(_makeObjcSignature(
func: func,
options: options,
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index 428056c..71082dd 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -474,13 +474,6 @@
}
for (final Api api in root.apis) {
for (final Method method in api.methods) {
- if (method.returnType.isNullable) {
- result.add(Error(
- message:
- 'Nullable return types types aren\'t supported for Pigeon methods: "${method.returnType.baseName}" in API: "${api.name}" method: "${method.name}"',
- lineNumber: _calculateLineNumberNullable(source, method.offset),
- ));
- }
if (method.arguments.isNotEmpty &&
method.arguments.any((NamedType element) =>
customEnums.contains(element.type.baseName))) {
diff --git a/packages/pigeon/pigeons/nullable_returns.dart b/packages/pigeon/pigeons/nullable_returns.dart
new file mode 100644
index 0000000..caa33ef
--- /dev/null
+++ b/packages/pigeon/pigeons/nullable_returns.dart
@@ -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.
+
+// This file is an example pigeon file that is used in compilation, unit, mock
+// handler, and e2e tests.
+
+import 'package:pigeon/pigeon.dart';
+
+@HostApi()
+abstract class NonNullHostApi {
+ int? doit();
+}
+
+@FlutterApi()
+abstract class NonNullFlutterApi {
+ int? doit();
+}
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 d71bddc..f318dc5 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
@@ -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 (v1.0.15), do not edit directly.
+// Autogenerated from Pigeon (v1.0.19), 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
@@ -130,7 +130,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -140,6 +139,11 @@
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 Everything?)!;
}
@@ -155,7 +159,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -165,6 +168,11 @@
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 Everything?)!;
}
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 6421b9f..8c80603 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
@@ -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 (v1.0.15), do not edit directly.
+// Autogenerated from Pigeon (v1.0.19), 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
@@ -37,7 +37,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -47,6 +46,11 @@
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?)!;
}
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 c894284..97ea6a4 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 (v1.0.15), do not edit directly.
+// Autogenerated from Pigeon (v1.0.19), 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
@@ -28,7 +28,7 @@
static SearchRequest decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return SearchRequest(
- query: (pigeonMap['query'] as String?)!,
+ query: pigeonMap['query']! as String,
);
}
}
@@ -55,8 +55,8 @@
static SearchReply decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
return SearchReply(
- result: (pigeonMap['result'] as String?)!,
- error: (pigeonMap['error'] as String?)!,
+ result: pigeonMap['result']! as String,
+ error: pigeonMap['error']! as String,
indices: (pigeonMap['indices'] as List<Object?>?)!.cast<int?>(),
);
}
@@ -113,7 +113,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -123,6 +122,11 @@
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 SearchReply?)!;
}
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 5b3c5c0..5cce608 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
@@ -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 (v1.0.15), do not edit directly.
+// Autogenerated from Pigeon (v1.0.19), 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
@@ -162,7 +162,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -172,6 +171,11 @@
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 SearchReply?)!;
}
@@ -187,7 +191,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -197,6 +200,11 @@
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 SearchReplies?)!;
}
@@ -212,7 +220,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -222,6 +229,11 @@
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 SearchRequests?)!;
}
@@ -237,7 +249,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -247,6 +258,11 @@
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?)!;
}
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
new file mode 100644
index 0000000..f80c7d3
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -0,0 +1,80 @@
+// 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.
+//
+// Autogenerated from Pigeon (v1.0.19), 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
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+
+class _NonNullHostApiCodec extends StandardMessageCodec {
+ const _NonNullHostApiCodec();
+}
+
+class NonNullHostApi {
+ /// Constructor for [NonNullHostApi]. 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})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = _NonNullHostApiCodec();
+
+ Future<int?> doit() async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NonNullHostApi.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 int?);
+ }
+ }
+}
+
+class _NonNullFlutterApiCodec extends StandardMessageCodec {
+ const _NonNullFlutterApiCodec();
+}
+
+abstract class NonNullFlutterApi {
+ static const MessageCodec<Object?> codec = _NonNullFlutterApiCodec();
+
+ int? doit();
+ static void setup(NonNullFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NonNullFlutterApi.doit', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ // ignore message
+ final int? output = api.doit();
+ 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 780b412..b71adc3 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
@@ -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 (v1.0.15), do not edit directly.
+// Autogenerated from Pigeon (v1.0.19), 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
@@ -37,7 +37,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -47,6 +46,11 @@
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?)!;
}
@@ -62,7 +66,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -72,6 +75,11 @@
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 bool?)!;
}
@@ -87,7 +95,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -97,6 +104,11 @@
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 String?)!;
}
@@ -112,7 +124,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -122,6 +133,11 @@
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 double?)!;
}
@@ -137,7 +153,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -147,6 +162,11 @@
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 Map<Object?, Object?>?)!;
}
@@ -162,7 +182,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -172,6 +191,11 @@
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?>?)!;
}
@@ -187,7 +211,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -197,6 +220,11 @@
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 Int32List?)!;
}
@@ -212,7 +240,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -222,6 +249,11 @@
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<bool?>();
}
@@ -237,7 +269,6 @@
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
- details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
@@ -247,6 +278,11 @@
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 Map<Object?, Object?>?)!
.cast<String?, int?>();
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 b00362c..e412b78 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
@@ -77,4 +77,16 @@
final int result = await api.anInt(1);
expect(result, 1);
});
+
+ test('return null to nonnull', () async {
+ final BinaryMessenger mockMessenger = MockBinaryMessenger();
+ const String channel = 'dev.flutter.pigeon.Api.anInt';
+ when(mockMessenger.send(channel, any))
+ .thenAnswer((Invocation realInvocation) async {
+ return Api.codec.encodeMessage(<String?, Object?>{'result': null});
+ });
+ final Api api = Api(binaryMessenger: mockMessenger);
+ expect(() async => api.anInt(1),
+ throwsA(const TypeMatcher<PlatformException>()));
+ });
}
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 0a81cb2..ed58c5c 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,10 +3,11 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
+ 0D02163D27BC7B48009BD76F /* nullable_returns.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D02163C27BC7B48009BD76F /* nullable_returns.gen.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 */; };
@@ -64,6 +65,8 @@
/* End PBXCopyFilesBuildPhase section */
/* 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>"; };
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>"; };
@@ -193,6 +196,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 0D02163B27BC7B48009BD76F /* nullable_returns.gen.h */,
+ 0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */,
0DBD8C40279B741800E4FDBA /* non_null_fields.gen.h */,
0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */,
0DA5DFD426CC39D600D2354B /* multiple_arity.gen.h */,
@@ -411,6 +416,7 @@
0DD2E6BE2684031300A7D764 /* message.gen.m in Sources */,
0DD2E6BA2684031300A7D764 /* void_arg_host.gen.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ 0D02163D27BC7B48009BD76F /* nullable_returns.gen.m in Sources */,
0DD2E6BF2684031300A7D764 /* enum.gen.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index e3dba64..686205b 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.18 # This must match the version in lib/generator_tools.dart
+version: 1.0.19 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index a44a34b..60f3971 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -241,6 +241,7 @@
gen_ios_unittests_code ./pigeons/message.dart ""
gen_ios_unittests_code ./pigeons/multiple_arity.dart ""
gen_ios_unittests_code ./pigeons/non_null_fields.dart "NNF"
+ gen_ios_unittests_code ./pigeons/nullable_returns.dart "NR"
gen_ios_unittests_code ./pigeons/primitive.dart ""
gen_ios_unittests_code ./pigeons/void_arg_flutter.dart "VAF"
gen_ios_unittests_code ./pigeons/void_arg_host.dart "VAH"
@@ -300,6 +301,7 @@
gen_android_unittests_code ./pigeons/message.dart MessagePigeon
gen_android_unittests_code ./pigeons/multiple_arity.dart MultipleArity
gen_android_unittests_code ./pigeons/non_null_fields.dart NonNullFields
+ gen_android_unittests_code ./pigeons/nullable_returns.dart NullableReturns
gen_android_unittests_code ./pigeons/primitive.dart Primitive
gen_android_unittests_code ./pigeons/void_arg_flutter.dart VoidArgFlutter
gen_android_unittests_code ./pigeons/void_arg_host.dart VoidArgHost
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 8a9dc51..647376b 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -945,4 +945,123 @@
'final List<int?>? arg_foo = (args[0] as List<Object?>?)?.cast<int?>()'));
expect(code, contains('final List<int?> output = api.doit(arg_foo!)'));
});
+
+ test('return nullable host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(isNullSafe: true), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('Future<int?> doit()'));
+ expect(code, contains('return (replyMap[\'result\'] as int?);'));
+ });
+
+ test('return nullable async host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[],
+ isAsynchronous: 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<int?> doit()'));
+ expect(code, contains('return (replyMap[\'result\'] as int?);'));
+ });
+
+ test('return nullable flutter', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(isNullSafe: true), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('int? doit();'));
+ expect(code, contains('final int? output = api.doit();'));
+ });
+
+ test('return nullable async flutter', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[],
+ isAsynchronous: 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<int?> doit();'));
+ expect(code, contains('final int? output = await api.doit();'));
+ });
+
+ test('platform error for return nil on nonnull', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: false,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateDart(const DartOptions(isNullSafe: true), root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ contains(
+ 'Host platform returned null value for non-null return value.'));
+ });
}
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index f162d3c..95f4652 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -874,4 +874,52 @@
contains(
'channel.send(new ArrayList<Object>(Arrays.asList(xArg, yArg)), channelReply ->'));
});
+
+ test('return nullable host', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ 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('@Nullable Long doit();'));
+ });
+
+ test('return nullable host async', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ isAsynchronous: true,
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+ generateJava(javaOptions, root, sink);
+ final String code = sink.toString();
+ // Java doesn't accept nullability annotations in type arguments.
+ expect(code, contains('Result<Long>'));
+ });
}
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index f534441..c03738c 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -187,6 +187,7 @@
expect(code, contains('@interface Input'));
expect(code, contains('@interface Output'));
expect(code, contains('@protocol Api'));
+ expect(code, contains('/// @return `nil` only when `error != nil`.'));
expect(code, matches('nullable Output.*doSomething.*Input.*FlutterError'));
expect(code, matches('ApiSetup.*<Api>.*_Nullable'));
});
@@ -748,7 +749,7 @@
expect(
code,
contains(
- '(void)doSomethingWithCompletion:(void(^)(ABCOutput *, NSError *_Nullable))completion'));
+ '(void)doSomethingWithCompletion:(void(^)(ABCOutput *_Nullable, NSError *_Nullable))completion'));
});
test('gen flutter void arg source', () {
@@ -775,7 +776,7 @@
expect(
code,
contains(
- '(void)doSomethingWithCompletion:(void(^)(ABCOutput *, NSError *_Nullable))completion'));
+ '(void)doSomethingWithCompletion:(void(^)(ABCOutput *_Nullable, NSError *_Nullable))completion'));
expect(code, contains('channel sendMessage:nil'));
});
@@ -1484,7 +1485,7 @@
expect(
code,
contains(
- '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void(^)(NSNumber *, NSError *_Nullable))completion;'));
+ '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void(^)(NSNumber *_Nullable, NSError *_Nullable))completion;'));
}
{
final StringBuffer sink = StringBuffer();
@@ -1494,7 +1495,7 @@
expect(
code,
contains(
- '- (void)addX:(NSNumber *)arg_x y:(NSNumber *)arg_y completion:(void(^)(NSNumber *, NSError *_Nullable))completion {'));
+ '- (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:'));
}
});
@@ -1582,4 +1583,73 @@
expect(code, contains('@interface Foobar'));
expect(code, contains('@property(nonatomic, copy) NSString * field1'));
});
+
+ test('return nullable flutter header', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateObjcHeader(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(
+ code,
+ matches(
+ r'doitWithCompletion.*void.*NSNumber \*_Nullable.*NSError.*completion;'));
+ });
+
+ test('return nullable flutter source', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateObjcSource(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, matches(r'doitWithCompletion.*NSNumber \*_Nullable'));
+ });
+
+ test('return nullable host header', () {
+ final Root root = Root(
+ apis: <Api>[
+ Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+ Method(
+ name: 'doit',
+ returnType: const TypeDeclaration(
+ baseName: 'int',
+ isNullable: true,
+ ),
+ arguments: <NamedType>[])
+ ])
+ ],
+ classes: <Class>[],
+ enums: <Enum>[],
+ );
+ final StringBuffer sink = StringBuffer();
+ generateObjcHeader(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, matches(r'nullable NSNumber.*doitWithError'));
+ });
}
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 512b165..1d21f46 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -581,23 +581,6 @@
expect(results.errors[0].message, contains('Nullable'));
});
- test('nullable api return', () {
- const String code = '''
-class Foo {
- int? x;
-}
-
-@HostApi()
-abstract class Api {
- Foo? doit(Foo foo);
-}
-''';
- 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);
@@ -1049,4 +1032,17 @@
final PigeonOptions options = PigeonOptions.fromMap(results.pigeonOptions!);
expect(options.objcOptions!.copyrightHeader, <String>['A', 'Header']);
});
+
+ test('return nullable', () {
+ const String code = '''
+@HostApi()
+abstract class Api {
+ int? calc();
+}
+''';
+
+ final ParseResults results = _parseSource(code);
+ expect(results.errors.length, 0);
+ expect(results.root.apis[0].methods[0].returnType.isNullable, isTrue);
+ });
}