[pigeon] Added non-null fields (#549)
* [pigeon] Added non-null types for fields
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index f4f3006..e85ec48 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,7 +1,12 @@
-## NEXT
+## 1.0.16
* Updates behavior of run\_tests.dart with no arguments.
* [debugging] Adds `ast_out` to help with debugging the compiler front-end.
+* [front-end, dart] Adds support for non-null fields in data classes in the
+ front-end parser and the Dart generator (unsupported languages ignore the
+ designation currently).
+* [front-end, dart, objc, java] Adds support for non-null fields in data
+ classes.
## 1.0.15
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index f9cc130..9dfcec7 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) Fields on classes currently must be nullable, arguments and return values to
- methods must be non-nullable.
+1) Arguments and return values to methods must be non-nullable. Fields on
+ classes can be nullable or non-nullable.
## Supported Datatypes
@@ -120,8 +120,15 @@
### Null Safety (NNBD)
-Right now Pigeon supports generating null-safe code, but it doesn't yet support
-[non-null fields](https://github.com/flutter/flutter/issues/59118).
+Pigeon supports generating null-safe code, but it doesn't yet support:
+
+1) Nullable method parameters
+1) Nullable generics type arguments
+1) Nullable return values
+
+It does support:
+
+1) Nullable and Non-nullable class fields.
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 678df52..53e82c1 100644
--- a/packages/pigeon/bin/run_tests.dart
+++ b/packages/pigeon/bin/run_tests.dart
@@ -87,53 +87,47 @@
return exitCode;
}
+/// Generates multiple dart files based on the jobs defined in [jobs] which is
+/// in the format of (key: input pigeon file path, value: output dart file
+/// path).
+Future<int> _generateDart(Map<String, String> jobs) async {
+ for (final MapEntry<String, String> job in jobs.entries) {
+ // TODO(gaaclarke): Make this run the jobs in parallel. A bug in Dart
+ // blocked this (https://github.com/dart-lang/pub/pull/3285).
+ final int result = await _runPigeon(
+ input: job.key, dartOut: job.value, streamOutput: false);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+}
+
Future<int> _runFlutterUnitTests() async {
const String flutterUnitTestsPath =
'platform_tests/flutter_null_safe_unit_tests';
- int generateCode = await _runPigeon(
- input: 'pigeons/flutter_unittests.dart',
- dartOut: '$flutterUnitTestsPath/lib/null_safe_pigeon.dart',
- );
- if (generateCode != 0) {
- return generateCode;
- }
- generateCode = await _runPigeon(
- input: 'pigeons/all_datatypes.dart',
- dartOut: '$flutterUnitTestsPath/lib/all_datatypes.dart',
- );
- if (generateCode != 0) {
- return generateCode;
- }
- generateCode = await _runPigeon(
- input: 'pigeons/primitive.dart',
- dartOut: '$flutterUnitTestsPath/lib/primitive.dart',
- );
- if (generateCode != 0) {
- return generateCode;
- }
- generateCode = await _runPigeon(
- input: 'pigeons/multiple_arity.dart',
- dartOut: '$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
- );
+ final int generateCode = await _generateDart(<String, String>{
+ 'pigeons/flutter_unittests.dart':
+ '$flutterUnitTestsPath/lib/null_safe_pigeon.dart',
+ 'pigeons/all_datatypes.dart':
+ '$flutterUnitTestsPath/lib/all_datatypes.dart',
+ 'pigeons/primitive.dart': '$flutterUnitTestsPath/lib/primitive.dart',
+ 'pigeons/multiple_arity.dart':
+ '$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
+ 'pigeons/non_null_fields.dart':
+ '$flutterUnitTestsPath/lib/non_null_fields.gen.dart',
+ });
if (generateCode != 0) {
return generateCode;
}
- const List<String> testFiles = <String>[
- 'null_safe_test.dart',
- 'all_datatypes_test.dart',
- 'primitive_test.dart',
- 'multiple_arity_test.dart'
- ];
- for (final String testFile in testFiles) {
- final int testCode = await _runProcess(
- 'flutter',
- <String>['test', 'test/$testFile'],
- workingDirectory: flutterUnitTestsPath,
- );
- if (testCode != 0) {
- return testCode;
- }
+ final int testCode = await _runProcess(
+ 'flutter',
+ <String>['test'],
+ workingDirectory: flutterUnitTestsPath,
+ );
+ if (testCode != 0) {
+ return testCode;
}
return 0;
@@ -174,7 +168,8 @@
String? cppHeaderOut,
String? cppSourceOut,
String? dartOut,
- String? dartTestOut}) async {
+ String? dartTestOut,
+ bool streamOutput = true}) async {
const bool hasDart = false;
final List<String> args = <String>[
'pub',
@@ -182,6 +177,8 @@
'pigeon',
'--input',
input,
+ '--copyright_header',
+ './copyright_header.txt',
];
if (cppHeaderOut != null) {
args.addAll(<String>[
@@ -204,9 +201,16 @@
if (!hasDart) {
args.add('--one_language');
}
- final Process generate = await _streamOutput(Process.start('dart', args));
+ final Process generate = streamOutput
+ ? await _streamOutput(Process.start('dart', args))
+ : await Process.start('dart', args);
final int generateCode = await generate.exitCode;
if (generateCode != 0) {
+ if (!streamOutput) {
+ print('dart $args failed:');
+ generate.stdout.pipe(stdout);
+ generate.stderr.pipe(stderr);
+ }
return generateCode;
}
return 0;
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 625b1d0..29893dc 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -50,8 +50,13 @@
.replaceAll(r"'", r"\'");
}
+/// Calculates the name of the codec class that will be generated for [api].
String _getCodecName(Api api) => '_${api.name}Codec';
+/// Writes the codec that will be used by [api].
+/// Example:
+///
+/// class FooCodec extends StandardMessageCodec {...}
void _writeCodec(Indent indent, String codecName, Api api, Root root) {
indent.write('class $codecName extends StandardMessageCodec ');
indent.scoped('{', '}', () {
@@ -113,9 +118,12 @@
String _getSafeArgumentName(int count, NamedType field) =>
field.name.isEmpty ? 'arg$count' : 'arg_' + field.name;
+/// Generates an argument name if one isn't defined.
String _getArgumentName(int count, NamedType field) =>
field.name.isEmpty ? 'arg$count' : field.name;
+/// Generates the arguments code for [func]
+/// Example: (func, getArgumentName, nullTag) -> 'String? foo, int bar'
String _getMethodArgumentsSignature(
Method func,
String Function(int index, NamedType arg) getArgumentName,
@@ -130,6 +138,15 @@
}).join(', ');
}
+/// Writes the code for host [Api], [api].
+/// Example:
+/// class FooCodec extends StandardMessageCodec {...}
+///
+/// class Foo {
+/// Foo(BinaryMessenger? binaryMessenger) {}
+/// static const MessageCodec<Object?> codec = FooCodec();
+/// Future<int> add(int x, int y) async {...}
+/// }
void _writeHostApi(DartOptions opt, Indent indent, Api api, Root root) {
assert(api.location == ApiLocation.host);
final String codecName = _getCodecName(api);
@@ -207,6 +224,15 @@
});
}
+/// Writes the code for host [Api], [api].
+/// Example:
+/// class FooCodec extends StandardMessageCodec {...}
+///
+/// abstract class Foo {
+/// static const MessageCodec<Object?> codec = FooCodec();
+/// int add(int x, int y);
+/// static void setup(Foo api, {BinaryMessenger? binaryMessenger}) {...}
+/// }
void _writeFlutterApi(
DartOptions opt,
Indent indent,
@@ -360,7 +386,8 @@
}
String _addGenericTypesNullable(NamedType field, String nullTag) {
- return '${_addGenericTypes(field.type, nullTag)}$nullTag';
+ final String genericdType = _addGenericTypes(field.type, nullTag);
+ return field.type.isNullable ? '$genericdType$nullTag' : genericdType;
}
/// Generates Dart source code for the given AST represented by [root],
@@ -373,43 +400,54 @@
final List<String> customEnumNames =
root.enums.map((Enum x) => x.name).toList();
final Indent indent = Indent(sink);
- if (opt.copyrightHeader != null) {
- addLines(indent, opt.copyrightHeader!, linePrefix: '// ');
+
+ void writeHeader() {
+ if (opt.copyrightHeader != null) {
+ addLines(indent, opt.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
+ indent.writeln(
+ '// 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',
+ );
+ indent.writeln('// @dart = ${opt.isNullSafe ? '2.12' : '2.8'}');
}
- indent.writeln('// $generatedCodeWarning');
- indent.writeln('// $seeAlsoWarning');
- indent.writeln(
- '// 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',
- );
- indent.writeln('// @dart = ${opt.isNullSafe ? '2.12' : '2.8'}');
- indent.writeln('import \'dart:async\';');
- indent.writeln(
- 'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
- );
- indent.addln('');
- indent.writeln(
- 'import \'package:flutter/foundation.dart\' show WriteBuffer, ReadBuffer;');
- indent.writeln('import \'package:flutter/services.dart\';');
- for (final Enum anEnum in root.enums) {
- indent.writeln('');
- indent.write('enum ${anEnum.name} ');
- indent.scoped('{', '}', () {
- for (final String member in anEnum.members) {
- indent.writeln('$member,');
- }
- });
+
+ void writeEnums() {
+ for (final Enum anEnum in root.enums) {
+ indent.writeln('');
+ indent.write('enum ${anEnum.name} ');
+ indent.scoped('{', '}', () {
+ for (final String member in anEnum.members) {
+ indent.writeln('$member,');
+ }
+ });
+ }
}
- for (final Class klass in root.classes) {
- indent.writeln('');
- indent.write('class ${klass.name} ');
- indent.scoped('{', '}', () {
- for (final NamedType field in klass.fields) {
- final String datatype = _addGenericTypesNullable(field, nullTag);
- indent.writeln('$datatype ${field.name};');
- }
- if (klass.fields.isNotEmpty) {
- indent.writeln('');
- }
+
+ void writeImports() {
+ indent.writeln('import \'dart:async\';');
+ indent.writeln(
+ 'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
+ );
+ indent.addln('');
+ indent.writeln(
+ 'import \'package:flutter/foundation.dart\' show WriteBuffer, ReadBuffer;');
+ indent.writeln('import \'package:flutter/services.dart\';');
+ }
+
+ void writeDataClass(Class klass) {
+ void writeConstructor() {
+ indent.write(klass.name);
+ indent.scoped('({', '});', () {
+ for (final NamedType field in klass.fields) {
+ final String required = field.type.isNullable ? '' : 'required ';
+ indent.writeln('${required}this.${field.name},');
+ }
+ });
+ }
+
+ void writeEncode() {
indent.write('Object encode() ');
indent.scoped('{', '}', () {
indent.writeln(
@@ -431,7 +469,43 @@
}
indent.writeln('return pigeonMap;');
});
- indent.writeln('');
+ }
+
+ void writeDecode() {
+ void writeValueDecode(NamedType field) {
+ if (customClassNames.contains(field.type.baseName)) {
+ indent.format('''
+pigeonMap['${field.name}'] != null
+\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']$unwrapOperator)
+\t\t: null''', leadingSpace: false, trailingNewline: false);
+ } else if (customEnumNames.contains(field.type.baseName)) {
+ indent.format('''
+pigeonMap['${field.name}'] != null
+\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']$unwrapOperator as int]
+\t\t: null''', leadingSpace: false, trailingNewline: false);
+ } else if (field.type.typeArguments.isNotEmpty) {
+ final String genericType =
+ _makeGenericTypeArguments(field.type, nullTag);
+ final String castCall = _makeGenericCastCall(field.type, nullTag);
+ final String castCallPrefix =
+ field.type.isNullable ? nullTag : unwrapOperator;
+ indent.add(
+ '(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$castCallPrefix$castCall',
+ );
+ } else {
+ final String genericdType = _addGenericTypesNullable(field, nullTag);
+ if (field.type.isNullable) {
+ indent.add(
+ 'pigeonMap[\'${field.name}\'] as $genericdType',
+ );
+ } else {
+ indent.add(
+ 'pigeonMap[\'${field.name}\']$unwrapOperator as $genericdType',
+ );
+ }
+ }
+ }
+
indent.write(
'static ${klass.name} decode(Object message) ',
);
@@ -439,47 +513,54 @@
indent.writeln(
'final Map<Object$nullTag, Object$nullTag> pigeonMap = message as Map<Object$nullTag, Object$nullTag>;',
);
- indent.writeln('return ${klass.name}()');
- indent.nest(1, () {
+ indent.write('return ${klass.name}');
+ indent.scoped('(', ');', () {
for (int index = 0; index < klass.fields.length; index += 1) {
final NamedType field = klass.fields[index];
- indent.write('..${field.name} = ');
- if (customClassNames.contains(field.type.baseName)) {
- indent.format('''
-pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']$unwrapOperator)
-\t\t: null''', leadingSpace: false, trailingNewline: false);
- } else if (customEnumNames.contains(field.type.baseName)) {
- indent.format('''
-pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']$unwrapOperator as int]
-\t\t: null''', leadingSpace: false, trailingNewline: false);
- } else if (field.type.typeArguments.isNotEmpty) {
- final String genericType =
- _makeGenericTypeArguments(field.type, nullTag);
- final String castCall = _makeGenericCastCall(field.type, nullTag);
- indent.add(
- '(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$nullTag$castCall',
- );
- } else {
- indent.add(
- 'pigeonMap[\'${field.name}\'] as ${_addGenericTypesNullable(field, nullTag)}',
- );
- }
- indent.addln(index == klass.fields.length - 1 ? ';' : '');
+ indent.write('${field.name}: ');
+ writeValueDecode(field);
+ indent.addln(',');
}
});
});
+ }
+
+ indent.write('class ${klass.name} ');
+ indent.scoped('{', '}', () {
+ writeConstructor();
+ indent.addln('');
+ for (final NamedType field in klass.fields) {
+ final String datatype = _addGenericTypesNullable(field, nullTag);
+ indent.writeln('$datatype ${field.name};');
+ }
+ if (klass.fields.isNotEmpty) {
+ indent.writeln('');
+ }
+ writeEncode();
+ indent.writeln('');
+ writeDecode();
});
}
- for (final Api api in root.apis) {
- indent.writeln('');
+
+ void writeApi(Api api) {
if (api.location == ApiLocation.host) {
_writeHostApi(opt, indent, api, root);
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApi(opt, indent, api, root);
}
}
+
+ writeHeader();
+ writeImports();
+ writeEnums();
+ for (final Class klass in root.classes) {
+ indent.writeln('');
+ writeDataClass(klass);
+ }
+ for (final Api api in root.apis) {
+ indent.writeln('');
+ writeApi(api);
+ }
}
/// Generates Dart source code for test support libraries based on the
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index e844daa..1dfbcdf 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.15';
+const String pigeonVersion = '1.0.16';
/// Read all the content from [stdin] to a String.
String readStdin() {
@@ -21,6 +21,9 @@
return utf8.decode(bytes);
}
+/// True if the generator line number should be printed out at the end of newlines.
+bool debugGenerators = false;
+
/// A helper class for managing indentation, wrapping a [StringSink].
class Indent {
/// Constructor which takes a [StringSink] [Ident] will wrap.
@@ -30,7 +33,16 @@
final StringSink _sink;
/// String used for newlines (ex "\n").
- final String newline = '\n';
+ String get newline {
+ if (debugGenerators) {
+ final List<String> frames = StackTrace.current.toString().split('\n');
+ return ' //' +
+ frames.firstWhere((String x) => x.contains('_generator.dart')) +
+ '\n';
+ } else {
+ return '\n';
+ }
+ }
/// String used to represent a tab.
final String tab = ' ';
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index c26205b..642d143 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -53,8 +53,12 @@
}
}
+/// Calculates the name of the codec that will be generated for [api].
String _getCodecName(Api api) => '${api.name}Codec';
+/// Writes the codec class that will be used by [api].
+/// Example:
+/// private static class FooCodec extends StandardMessageCodec {...}
void _writeCodec(Indent indent, Api api, Root root) {
final String codecName = _getCodecName(api);
indent.write('private static class $codecName extends StandardMessageCodec ');
@@ -103,6 +107,12 @@
});
}
+/// Write the java code that represents a host [Api], [api].
+/// Example:
+/// public interface Foo {
+/// int add(int x, int y);
+/// static void setup(BinaryMessenger binaryMessenger, Foo api) {...}
+/// }
void _writeHostApi(Indent indent, Api api) {
assert(api.location == ApiLocation.host);
@@ -249,6 +259,15 @@
String _getSafeArgumentName(int count, NamedType argument) =>
_getArgumentName(count, argument) + 'Arg';
+/// Writes the code for a flutter [Api], [api].
+/// Example:
+/// public static class Foo {
+/// public Foo(BinaryMessenger argBinaryMessenger) {...}
+/// public interface Reply<T> {
+/// void reply(T reply);
+/// }
+/// public int add(int x, int y, Reply<int> callback) {...}
+/// }
void _writeFlutterApi(Indent indent, Api api) {
assert(api.location == ApiLocation.flutter);
indent.writeln(
@@ -397,138 +416,192 @@
final Set<String> rootEnumNameSet =
root.enums.map((Enum x) => x.name).toSet();
final Indent indent = Indent(sink);
- if (options.copyrightHeader != null) {
- addLines(indent, options.copyrightHeader!, linePrefix: '// ');
- }
- indent.writeln('// $generatedCodeWarning');
- indent.writeln('// $seeAlsoWarning');
- indent.addln('');
- if (options.package != null) {
- indent.writeln('package ${options.package};');
- }
- indent.addln('');
- indent.writeln('import android.util.Log;');
- indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
- indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
- indent.writeln('import io.flutter.plugin.common.MessageCodec;');
- indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
- indent.writeln('import java.io.ByteArrayOutputStream;');
- indent.writeln('import java.nio.ByteBuffer;');
- indent.writeln('import java.util.Arrays;');
- indent.writeln('import java.util.ArrayList;');
- indent.writeln('import java.util.List;');
- indent.writeln('import java.util.Map;');
- indent.writeln('import java.util.HashMap;');
- indent.addln('');
- indent.writeln('/** Generated class from Pigeon. */');
- indent.writeln(
- '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})');
- indent.write('public class ${options.className!} ');
- indent.scoped('{', '}', () {
- for (final Enum anEnum in root.enums) {
+ void writeHeader() {
+ if (options.copyrightHeader != null) {
+ addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
+ }
+
+ void writeImports() {
+ indent.writeln('import android.util.Log;');
+ indent.writeln('import androidx.annotation.NonNull;');
+ indent.writeln('import androidx.annotation.Nullable;');
+ indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
+ indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
+ indent.writeln('import io.flutter.plugin.common.MessageCodec;');
+ indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
+ indent.writeln('import java.io.ByteArrayOutputStream;');
+ indent.writeln('import java.nio.ByteBuffer;');
+ indent.writeln('import java.util.Arrays;');
+ indent.writeln('import java.util.ArrayList;');
+ indent.writeln('import java.util.List;');
+ indent.writeln('import java.util.Map;');
+ indent.writeln('import java.util.HashMap;');
+ }
+
+ void writeEnum(Enum anEnum) {
+ indent.write('public enum ${anEnum.name} ');
+ indent.scoped('{', '}', () {
+ int index = 0;
+ for (final String member in anEnum.members) {
+ indent.writeln(
+ '$member($index)${index == anEnum.members.length - 1 ? ';' : ','}');
+ index++;
+ }
indent.writeln('');
- indent.write('public enum ${anEnum.name} ');
+ // We use explicit indexing here as use of the ordinal() method is
+ // discouraged. The toMap and fromMap API matches class API to allow
+ // the same code to work with enums and classes, but this
+ // can also be done directly in the host and flutter APIs.
+ indent.writeln('private int index;');
+ indent.write('private ${anEnum.name}(final int index) ');
indent.scoped('{', '}', () {
- int index = 0;
- for (final String member in anEnum.members) {
- indent.writeln(
- '$member($index)${index == anEnum.members.length - 1 ? ';' : ','}');
- index++;
+ indent.writeln('this.index = index;');
+ });
+ });
+ }
+
+ void writeDataClass(Class klass) {
+ void writeField(NamedType field) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ final String nullability =
+ field.type.isNullable ? '@Nullable' : '@NonNull';
+ indent.writeln(
+ 'private $nullability ${hostDatatype.datatype} ${field.name};');
+ indent.writeln(
+ 'public $nullability ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
+ indent.writeScoped(
+ 'public void ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {',
+ '}', () {
+ if (!field.type.isNullable) {
+ indent.writeScoped('if (setterArg == null) {', '}', () {
+ indent.writeln(
+ 'throw new IllegalStateException("Nonnull field \\"${field.name}\\" is null.");');
+ });
}
- indent.writeln('');
- // We use explicit indexing here as use of the ordinal() method is
- // discouraged. The toMap and fromMap API matches class API to allow
- // the same code to work with enums and classes, but this
- // can also be done directly in the host and flutter APIs.
- indent.writeln('private int index;');
- indent.write('private ${anEnum.name}(final int index) ');
- indent.scoped('{', '}', () {
- indent.writeln('this.index = index;');
- });
+ indent.writeln('this.${field.name} = setterArg;');
});
}
- for (final Class klass in root.classes) {
- indent.addln('');
- indent.writeln(
- '/** Generated class from Pigeon that represents data sent in messages. */');
- indent.write('public static class ${klass.name} ');
+ void writeToMap() {
+ indent.write('@NonNull Map<String, Object> toMap() ');
+ indent.scoped('{', '}', () {
+ indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
+ for (final NamedType field in klass.fields) {
+ final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+ root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+ String toWriteValue = '';
+ final String fieldName = field.name;
+ if (!hostDatatype.isBuiltin &&
+ rootClassNameSet.contains(field.type.baseName)) {
+ toWriteValue = '($fieldName == null) ? null : $fieldName.toMap()';
+ } else if (!hostDatatype.isBuiltin &&
+ rootEnumNameSet.contains(field.type.baseName)) {
+ toWriteValue = '$fieldName == null ? null : $fieldName.index';
+ } else {
+ toWriteValue = field.name;
+ }
+ indent.writeln('toMapResult.put("${field.name}", $toWriteValue);');
+ }
+ indent.writeln('return toMapResult;');
+ });
+ }
+
+ void writeFromMap() {
+ indent.write(
+ 'static @NonNull ${klass.name} fromMap(@NonNull Map<String, Object> map) ');
+ indent.scoped('{', '}', () {
+ const String result = 'pigeonResult';
+ indent.writeln('${klass.name} $result = new ${klass.name}();');
+ for (final NamedType field in klass.fields) {
+ final String fieldVariable = field.name;
+ final String setter = _makeSetter(field);
+ indent.writeln('Object $fieldVariable = map.get("${field.name}");');
+ if (rootEnumNameSet.contains(field.type.baseName)) {
+ indent.writeln(
+ '$result.$setter($fieldVariable == null ? null : ${field.type.baseName}.values()[(int)$fieldVariable]);');
+ } else {
+ indent.writeln(
+ '$result.$setter(${_castObject(field, root.classes, root.enums, fieldVariable)});');
+ }
+ }
+ indent.writeln('return $result;');
+ });
+ }
+
+ void writeBuilder() {
+ indent.write('public static class Builder ');
indent.scoped('{', '}', () {
for (final NamedType field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
- indent.writeln('private ${hostDatatype.datatype} ${field.name};');
+ final String nullability =
+ field.type.isNullable ? '@Nullable' : '@NonNull';
indent.writeln(
- 'public ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
- indent.writeln(
- 'public void ${_makeSetter(field)}(${hostDatatype.datatype} setterArg) { this.${field.name} = setterArg; }');
- indent.addln('');
+ 'private @Nullable ${hostDatatype.datatype} ${field.name};');
+ indent.writeScoped(
+ 'public @NonNull Builder ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {',
+ '}', () {
+ indent.writeln('this.${field.name} = setterArg;');
+ indent.writeln('return this;');
+ });
}
- indent.write('Map<String, Object> toMap() ');
+ indent.write('public @NonNull ${klass.name} build() ');
indent.scoped('{', '}', () {
- indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
+ const String returnVal = 'pigeonReturn';
+ indent.writeln('${klass.name} $returnVal = new ${klass.name}();');
for (final NamedType field in klass.fields) {
- final HostDatatype hostDatatype = getHostDatatype(
- field,
- root.classes,
- root.enums,
- (NamedType x) => _javaTypeForBuiltinDartType(x.type));
- String toWriteValue = '';
- final String fieldName = field.name;
- if (!hostDatatype.isBuiltin &&
- rootClassNameSet.contains(field.type.baseName)) {
- toWriteValue = '($fieldName == null) ? null : $fieldName.toMap()';
- } else if (!hostDatatype.isBuiltin &&
- rootEnumNameSet.contains(field.type.baseName)) {
- toWriteValue = '$fieldName == null ? null : $fieldName.index';
- } else {
- toWriteValue = field.name;
- }
- indent.writeln('toMapResult.put("${field.name}", $toWriteValue);');
+ indent.writeln('$returnVal.${_makeSetter(field)}(${field.name});');
}
- indent.writeln('return toMapResult;');
- });
- indent.write('static ${klass.name} fromMap(Map<String, Object> map) ');
- indent.scoped('{', '}', () {
- indent.writeln('${klass.name} fromMapResult = new ${klass.name}();');
- for (final NamedType field in klass.fields) {
- final String fieldVariable = field.name;
- indent.writeln('Object $fieldVariable = map.get("${field.name}");');
- if (rootEnumNameSet.contains(field.type.baseName)) {
- indent.writeln(
- 'fromMapResult.${field.name} = $fieldVariable == null ? null : ${field.type.baseName}.values()[(int)$fieldVariable];');
- } else {
- indent.writeln(
- 'fromMapResult.${field.name} = ${_castObject(field, root.classes, root.enums, fieldVariable)};');
- }
- }
- indent.writeln('return fromMapResult;');
+ indent.writeln('return $returnVal;');
});
});
}
- if (root.apis.any((Api api) =>
- api.location == ApiLocation.host &&
- api.methods.any((Method it) => it.isAsynchronous))) {
- indent.addln('');
- indent.write('public interface Result<T> ');
- indent.scoped('{', '}', () {
- indent.writeln('void success(T result);');
- indent.writeln('void error(Throwable error);');
- });
- }
-
- for (final Api api in root.apis) {
- _writeCodec(indent, api, root);
- indent.addln('');
- if (api.location == ApiLocation.host) {
- _writeHostApi(indent, api);
- } else if (api.location == ApiLocation.flutter) {
- _writeFlutterApi(indent, api);
+ indent.writeln(
+ '/** Generated class from Pigeon that represents data sent in messages. */');
+ indent.write('public static class ${klass.name} ');
+ indent.scoped('{', '}', () {
+ for (final NamedType field in klass.fields) {
+ writeField(field);
+ indent.addln('');
}
- }
+ if (klass.fields
+ .map((NamedType e) => !e.type.isNullable)
+ .any((bool e) => e)) {
+ indent.writeln(
+ '/** Constructor is private to enforce null safety; use Builder. */');
+ indent.writeln('private ${klass.name}() {}');
+ }
+
+ writeBuilder();
+ writeToMap();
+ writeFromMap();
+ });
+ }
+
+ void writeResultInterface() {
+ indent.write('public interface Result<T> ');
+ indent.scoped('{', '}', () {
+ indent.writeln('void success(T result);');
+ indent.writeln('void error(Throwable error);');
+ });
+ }
+
+ void writeApi(Api api) {
+ if (api.location == ApiLocation.host) {
+ _writeHostApi(indent, api);
+ } else if (api.location == ApiLocation.flutter) {
+ _writeFlutterApi(indent, api);
+ }
+ }
+
+ void writeWrapError() {
indent.format('''
private static Map<String, Object> wrapError(Throwable exception) {
\tMap<String, Object> errorMap = new HashMap<>();
@@ -537,5 +610,44 @@
\terrorMap.put("${Keys.errorDetails}", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
\treturn errorMap;
}''');
+ }
+
+ writeHeader();
+ indent.addln('');
+ if (options.package != null) {
+ indent.writeln('package ${options.package};');
+ }
+ indent.addln('');
+ writeImports();
+ indent.addln('');
+ indent.writeln('/** Generated class from Pigeon. */');
+ indent.writeln(
+ '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})');
+ indent.write('public class ${options.className!} ');
+ indent.scoped('{', '}', () {
+ for (final Enum anEnum in root.enums) {
+ indent.writeln('');
+ writeEnum(anEnum);
+ }
+
+ for (final Class klass in root.classes) {
+ indent.addln('');
+ writeDataClass(klass);
+ }
+
+ if (root.apis.any((Api api) =>
+ api.location == ApiLocation.host &&
+ api.methods.any((Method it) => it.isAsynchronous))) {
+ indent.addln('');
+ writeResultInterface();
+ }
+
+ for (final Api api in root.apis) {
+ _writeCodec(indent, api, root);
+ indent.addln('');
+ writeApi(api);
+ }
+
+ writeWrapError();
});
}
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index da9c4be..e232407 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -54,6 +54,7 @@
}
}
+/// Calculates the ObjC class name, possibly prefixed.
String _className(String? prefix, String className) {
if (prefix != null) {
return '$prefix$className';
@@ -62,12 +63,14 @@
}
}
+/// Calculates callback block signature for for async methods.
String _callbackForType(TypeDeclaration type, _ObjcPtr objcType) {
return type.isVoid
? 'void(^)(NSError *_Nullable)'
: 'void(^)(${objcType.ptr.trim()}, NSError *_Nullable)';
}
+/// Represents an ObjC pointer (ex 'id', 'NSString *').
class _ObjcPtr {
const _ObjcPtr({required this.baseName}) : hasAsterisk = baseName != 'id';
final String baseName;
@@ -75,6 +78,7 @@
String get ptr => '$baseName${hasAsterisk ? ' *' : ' '}';
}
+/// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *').
const Map<String, _ObjcPtr> _objcTypeForDartTypeMap = <String, _ObjcPtr>{
'bool': _ObjcPtr(baseName: 'NSNumber'),
'int': _ObjcPtr(baseName: 'NSNumber'),
@@ -89,6 +93,9 @@
'Object': _ObjcPtr(baseName: 'id'),
};
+/// Converts list of [TypeDeclaration] to a code string representing the type
+/// arguments for use in generics.
+/// Example: ('FOO', ['Foo', 'Bar']) -> 'FOOFoo *, FOOBar *').
String _flattenTypeArguments(String? classPrefix, List<TypeDeclaration> args) {
final String result = args
.map<String>((TypeDeclaration e) =>
@@ -116,6 +123,7 @@
: _ObjcPtr(baseName: _className(classPrefix, field.baseName));
}
+/// Maps a type to a properties memory semantics (ie strong, copy).
String _propertyTypeForDartType(NamedType field) {
const Map<String, String> propertyTypeForDartTypeMap = <String, String>{
'String': 'copy',
@@ -138,11 +146,61 @@
}
}
+bool _isNullable(HostDatatype hostDatatype, TypeDeclaration type) =>
+ hostDatatype.datatype.contains('*') && type.isNullable;
+
+/// Writes the method declaration for the initializer.
+///
+/// Example '+ (instancetype)makeWithFoo:(NSString *)foo'
+void _writeInitializerDeclaration(Indent indent, Class klass,
+ List<Class> classes, List<Enum> enums, String? prefix) {
+ final List<String> enumNames = enums.map((Enum x) => x.name).toList();
+ indent.write('+ (instancetype)makeWith');
+ bool isFirst = true;
+ indent.nest(2, () {
+ for (final NamedType field in klass.fields) {
+ final String label = isFirst ? _capitalize(field.name) : field.name;
+ final void Function(String) printer = isFirst
+ ? indent.add
+ : (String x) {
+ indent.addln('');
+ indent.write(x);
+ };
+ isFirst = false;
+ final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
+ (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
+ customResolver: enumNames.contains(field.type.baseName)
+ ? (String x) => _className(prefix, x)
+ : (String x) => '${_className(prefix, x)} *');
+ final String nullable =
+ _isNullable(hostDatatype, field.type) ? 'nullable ' : '';
+ printer('$label:($nullable${hostDatatype.datatype})${field.name}');
+ }
+ });
+}
+
+/// Writes the class declaration for a data class.
+///
+/// Example:
+/// @interface Foo : NSObject
+/// @property (nonatomic, copy) NSString *bar;
+/// @end
void _writeClassDeclarations(
Indent indent, List<Class> classes, List<Enum> enums, String? prefix) {
final List<String> enumNames = enums.map((Enum x) => x.name).toList();
for (final Class klass in classes) {
indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject');
+ if (klass.fields.isNotEmpty) {
+ if (klass.fields
+ .map((NamedType e) => !e.type.isNullable)
+ .any((bool e) => e)) {
+ indent.writeln(
+ '/// `init` unavailable to enforce nonnull fields, see the `make` class method.');
+ indent.writeln('- (instancetype)init NS_UNAVAILABLE;');
+ }
+ _writeInitializerDeclaration(indent, klass, classes, enums, prefix);
+ indent.addln(';');
+ }
for (final NamedType field in klass.fields) {
final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
(NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
@@ -156,7 +214,7 @@
propertyType = _propertyTypeForDartType(field);
}
final String nullability =
- hostDatatype.datatype.contains('*') ? ', nullable' : '';
+ _isNullable(hostDatatype, field.type) ? ', nullable' : '';
indent.writeln(
'@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};');
}
@@ -165,12 +223,25 @@
}
}
+/// Generates the name of the codec that will be generated.
String _getCodecName(String? prefix, String className) =>
'${_className(prefix, className)}Codec';
+/// Generates the name of the function for accessing the codec instance used by
+/// the api class named [className].
String _getCodecGetterName(String? prefix, String className) =>
'${_className(prefix, className)}GetCodec';
+/// Writes the codec that will be used for encoding messages for the [api].
+///
+/// Example:
+/// @interface FooHostApiCodecReader : FlutterStandardReader
+/// ...
+/// @interface FooHostApiCodecWriter : FlutterStandardWriter
+/// ...
+/// @interface FooHostApiCodecReaderWriter : FlutterStandardReaderWriter
+/// ...
+/// NSObject<FlutterMessageCodec> *FooHostApiCodecGetCodec() {...}
void _writeCodec(
Indent indent, String name, ObjcOptions options, Api api, Root root) {
final String readerWriterName = '${name}ReaderWriter';
@@ -319,6 +390,14 @@
return '- ($returnType)$argSignature';
}
+/// Writes the declaration for an host [Api].
+///
+/// Example:
+/// @protocol Foo
+/// - (NSInteger)add:(NSInteger)x to:(NSInteger)y error:(NSError**)error;
+/// @end
+///
+/// extern void FooSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<Foo> *_Nullable api);
void _writeHostApiDeclaration(Indent indent, Api api, ObjcOptions options) {
final String apiName = _className(options.prefix, api.name);
indent.writeln('@protocol $apiName');
@@ -361,6 +440,14 @@
indent.writeln('');
}
+/// Writes the declaration for an flutter [Api].
+///
+/// Example:
+///
+/// @interface Foo : NSObject
+/// - (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
+/// - (void)add:(NSInteger)x to:(NSInteger)y completion:(void(^)(NSError *, NSInteger result)completion;
+/// @end
void _writeFlutterApiDeclaration(Indent indent, Api api, ObjcOptions options) {
final String apiName = _className(options.prefix, api.name);
indent.writeln('@interface $apiName : NSObject');
@@ -386,22 +473,27 @@
/// provided [options].
void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
final Indent indent = Indent(sink);
- if (options.copyrightHeader != null) {
- addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+
+ void writeHeader() {
+ if (options.copyrightHeader != null) {
+ addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
}
- indent.writeln('// $generatedCodeWarning');
- indent.writeln('// $seeAlsoWarning');
- indent.writeln('#import <Foundation/Foundation.h>');
- indent.writeln('@protocol FlutterBinaryMessenger;');
- indent.writeln('@protocol FlutterMessageCodec;');
- indent.writeln('@class FlutterError;');
- indent.writeln('@class FlutterStandardTypedData;');
- indent.writeln('');
- indent.writeln('NS_ASSUME_NONNULL_BEGIN');
+ void writeImports() {
+ indent.writeln('#import <Foundation/Foundation.h>');
+ }
- for (final Enum anEnum in root.enums) {
- indent.writeln('');
+ void writeForwardDeclarations() {
+ indent.writeln('@protocol FlutterBinaryMessenger;');
+ indent.writeln('@protocol FlutterMessageCodec;');
+ indent.writeln('@class FlutterError;');
+ indent.writeln('@class FlutterStandardTypedData;');
+ }
+
+ void writeEnum(Enum anEnum) {
final String enumName = _className(options.prefix, anEnum.name);
indent.write('typedef NS_ENUM(NSUInteger, $enumName) ');
indent.scoped('{', '};', () {
@@ -414,6 +506,18 @@
}
});
}
+
+ writeHeader();
+ writeImports();
+ writeForwardDeclarations();
+ indent.writeln('');
+
+ indent.writeln('NS_ASSUME_NONNULL_BEGIN');
+
+ for (final Enum anEnum in root.enums) {
+ indent.writeln('');
+ writeEnum(anEnum);
+ }
indent.writeln('');
for (final Class klass in root.classes) {
@@ -471,137 +575,163 @@
String _getSafeArgName(int count, NamedType arg) =>
arg.name.isEmpty ? 'arg$count' : 'arg_${arg.name}';
+/// Writes the definition code for a host [Api].
+/// See also: [_writeHostApiDeclaration]
void _writeHostApiSource(Indent indent, ObjcOptions options, Api api) {
assert(api.location == ApiLocation.host);
final String apiName = _className(options.prefix, api.name);
+
+ void writeChannelAllocation(Method func, String varName) {
+ indent.writeln('FlutterBasicMessageChannel *$varName =');
+ indent.inc();
+ indent.writeln('[FlutterBasicMessageChannel');
+ indent.inc();
+ indent.writeln('messageChannelWithName:@"${makeChannelName(api, func)}"');
+ indent.writeln('binaryMessenger:binaryMessenger');
+ indent
+ .writeln('codec:${_getCodecGetterName(options.prefix, api.name)}()];');
+ indent.dec();
+ indent.dec();
+ }
+
+ void writeChannelApiBinding(Method func, String channel) {
+ void unpackArgs(String variable, Iterable<String> argNames) {
+ indent.writeln('NSArray *args = $variable;');
+ 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];';
+ }).forEach(indent.writeln);
+ }
+
+ void writeAsyncBindings(Iterable<String> selectorComponents,
+ String callSignature, _ObjcPtr returnType) {
+ if (func.returnType.isVoid) {
+ const String callback = 'callback(wrapResult(nil, error));';
+ if (func.arguments.isEmpty) {
+ indent.writeScoped(
+ '[api ${selectorComponents.first}:^(FlutterError *_Nullable error) {',
+ '}];', () {
+ indent.writeln(callback);
+ });
+ } else {
+ indent.writeScoped(
+ '[api $callSignature ${selectorComponents.last}:^(FlutterError *_Nullable error) {',
+ '}];', () {
+ indent.writeln(callback);
+ });
+ }
+ } else {
+ const String callback = 'callback(wrapResult(output, error));';
+ if (func.arguments.isEmpty) {
+ indent.writeScoped(
+ '[api ${selectorComponents.first}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
+ '}];', () {
+ indent.writeln(callback);
+ });
+ } else {
+ indent.writeScoped(
+ '[api $callSignature ${selectorComponents.last}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
+ '}];', () {
+ indent.writeln(callback);
+ });
+ }
+ }
+ }
+
+ void writeSyncBindings(String call, _ObjcPtr returnType) {
+ indent.writeln('FlutterError *error;');
+ if (func.returnType.isVoid) {
+ indent.writeln('$call;');
+ indent.writeln('callback(wrapResult(nil, error));');
+ } else {
+ indent.writeln('${returnType.ptr}output = $call;');
+ indent.writeln('callback(wrapResult(output, error));');
+ }
+ }
+
+ // TODO(gaaclarke): Incorporate this into _getSelectorComponents.
+ final String lastSelectorComponent =
+ func.isAsynchronous ? 'completion' : 'error';
+ final String selector = _getSelector(func, lastSelectorComponent);
+ indent.writeln(
+ 'NSCAssert([api respondsToSelector:@selector($selector)], @"$apiName api (%@) doesn\'t respond to @selector($selector)", api);');
+ indent.write(
+ '[$channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ');
+ indent.scoped('{', '}];', () {
+ final _ObjcPtr returnType =
+ _objcTypeForDartType(options.prefix, func.returnType);
+ final Iterable<String> selectorComponents =
+ _getSelectorComponents(func, lastSelectorComponent);
+ final Iterable<String> argNames =
+ indexMap(func.arguments, _getSafeArgName);
+ final String callSignature =
+ map2(selectorComponents.take(argNames.length), argNames,
+ (String selectorComponent, String argName) {
+ return '$selectorComponent:$argName';
+ }).join(' ');
+ if (func.arguments.isNotEmpty) {
+ unpackArgs('message', argNames);
+ }
+ if (func.isAsynchronous) {
+ writeAsyncBindings(selectorComponents, callSignature, returnType);
+ } else {
+ final String syncCall = func.arguments.isEmpty
+ ? '[api ${selectorComponents.first}:&error]'
+ : '[api $callSignature error:&error]';
+ writeSyncBindings(syncCall, returnType);
+ }
+ });
+ }
+
+ const String channelName = 'channel';
indent.write(
'void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<$apiName> *api) ');
indent.scoped('{', '}', () {
for (final Method func in api.methods) {
indent.write('');
indent.scoped('{', '}', () {
- indent.writeln('FlutterBasicMessageChannel *channel =');
- indent.inc();
- indent.writeln('[FlutterBasicMessageChannel');
- indent.inc();
- indent
- .writeln('messageChannelWithName:@"${makeChannelName(api, func)}"');
- indent.writeln('binaryMessenger:binaryMessenger');
- indent.writeln(
- 'codec:${_getCodecGetterName(options.prefix, api.name)}()];');
- indent.dec();
- indent.dec();
-
+ writeChannelAllocation(func, channelName);
indent.write('if (api) ');
indent.scoped('{', '}', () {
- // TODO(gaaclarke): Incorporate this into _getSelectorComponents.
- final String lastSelectorComponent =
- func.isAsynchronous ? 'completion' : 'error';
- final String selector = _getSelector(func, lastSelectorComponent);
- indent.writeln(
- 'NSCAssert([api respondsToSelector:@selector($selector)], @"$apiName api (%@) doesn\'t respond to @selector($selector)", api);');
- indent.write(
- '[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ');
- indent.scoped('{', '}];', () {
- final _ObjcPtr returnType =
- _objcTypeForDartType(options.prefix, func.returnType);
- String syncCall;
- String? callSignature;
- final Iterable<String> selectorComponents =
- _getSelectorComponents(func, lastSelectorComponent);
- if (func.arguments.isEmpty) {
- syncCall = '[api ${selectorComponents.first}:&error]';
- } else {
- indent.writeln('NSArray *args = message;');
- final Iterable<String> argNames =
- indexMap(func.arguments, _getSafeArgName);
- 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];';
- }).forEach(indent.writeln);
- callSignature =
- map2(selectorComponents.take(argNames.length), argNames,
- (String selectorComponent, String argName) {
- return '$selectorComponent:$argName';
- }).join(' ');
- syncCall = '[api $callSignature error:&error]';
- }
- if (func.isAsynchronous) {
- if (func.returnType.isVoid) {
- const String callback = 'callback(wrapResult(nil, error));';
- if (func.arguments.isEmpty) {
- indent.writeScoped(
- '[api ${selectorComponents.first}:^(FlutterError *_Nullable error) {',
- '}];', () {
- indent.writeln(callback);
- });
- } else {
- indent.writeScoped(
- '[api $callSignature ${selectorComponents.last}:^(FlutterError *_Nullable error) {',
- '}];', () {
- indent.writeln(callback);
- });
- }
- } else {
- const String callback = 'callback(wrapResult(output, error));';
- if (func.arguments.isEmpty) {
- indent.writeScoped(
- '[api ${selectorComponents.first}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
- '}];', () {
- indent.writeln(callback);
- });
- } else {
- indent.writeScoped(
- '[api $callSignature ${selectorComponents.last}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
- '}];', () {
- indent.writeln(callback);
- });
- }
- }
- } else {
- indent.writeln('FlutterError *error;');
- if (func.returnType.isVoid) {
- indent.writeln('$syncCall;');
- indent.writeln('callback(wrapResult(nil, error));');
- } else {
- indent.writeln('${returnType.ptr}output = $syncCall;');
- indent.writeln('callback(wrapResult(output, error));');
- }
- }
- });
+ writeChannelApiBinding(func, channelName);
});
indent.write('else ');
indent.scoped('{', '}', () {
- indent.writeln('[channel setMessageHandler:nil];');
+ indent.writeln('[$channelName setMessageHandler:nil];');
});
});
}
});
}
+/// Writes the definition code for a flutter [Api].
+/// See also: [_writeFlutterApiDeclaration]
void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) {
assert(api.location == ApiLocation.flutter);
final String apiName = _className(options.prefix, api.name);
- indent.writeln('@interface $apiName ()');
- indent.writeln(
- '@property (nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;');
- indent.writeln('@end');
- indent.addln('');
- indent.writeln('@implementation $apiName');
- indent.write(
- '- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger ');
- indent.scoped('{', '}', () {
- indent.writeln('self = [super init];');
- indent.write('if (self) ');
+
+ void writeExtension() {
+ indent.writeln('@interface $apiName ()');
+ indent.writeln(
+ '@property (nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;');
+ indent.writeln('@end');
+ }
+
+ void writeInitializer() {
+ indent.write(
+ '- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger ');
indent.scoped('{', '}', () {
- indent.writeln('_binaryMessenger = binaryMessenger;');
+ indent.writeln('self = [super init];');
+ indent.write('if (self) ');
+ indent.scoped('{', '}', () {
+ indent.writeln('_binaryMessenger = binaryMessenger;');
+ });
+ indent.writeln('return self;');
});
- indent.writeln('return self;');
- });
- indent.addln('');
- for (final Method func in api.methods) {
+ }
+
+ void writeMethod(Method func) {
final _ObjcPtr returnType =
_objcTypeForDartType(options.prefix, func.returnType);
final String callbackType = _callbackForType(func.returnType, returnType);
@@ -644,6 +774,13 @@
});
});
}
+
+ writeExtension();
+ indent.addln('');
+ indent.writeln('@implementation $apiName');
+ indent.addln('');
+ writeInitializer();
+ api.methods.forEach(writeMethod);
indent.writeln('@end');
}
@@ -655,21 +792,27 @@
root.classes.map((Class x) => x.name).toList();
final List<String> enumNames = root.enums.map((Enum x) => x.name).toList();
- if (options.copyrightHeader != null) {
- addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ void writeHeader() {
+ if (options.copyrightHeader != null) {
+ addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+ }
+ indent.writeln('// $generatedCodeWarning');
+ indent.writeln('// $seeAlsoWarning');
}
- indent.writeln('// $generatedCodeWarning');
- indent.writeln('// $seeAlsoWarning');
- indent.writeln('#import "${options.header}"');
- indent.writeln('#import <Flutter/Flutter.h>');
- indent.writeln('');
- indent.writeln('#if !__has_feature(objc_arc)');
- indent.writeln('#error File requires ARC to be enabled.');
- indent.writeln('#endif');
- indent.addln('');
+ void writeImports() {
+ indent.writeln('#import "${options.header}"');
+ indent.writeln('#import <Flutter/Flutter.h>');
+ }
- indent.format('''
+ void writeArcEnforcer() {
+ indent.writeln('#if !__has_feature(objc_arc)');
+ indent.writeln('#error File requires ARC to be enabled.');
+ indent.writeln('#endif');
+ }
+
+ void writeWrapResultFunction() {
+ indent.format('''
static NSDictionary<NSString *, id> *wrapResult(id result, FlutterError *error) {
\tNSDictionary *errorDict = (NSDictionary *)[NSNull null];
\tif (error) {
@@ -684,9 +827,9 @@
\t\t\t@"${Keys.error}": errorDict,
\t\t\t};
}''');
- indent.addln('');
+ }
- for (final Class klass in root.classes) {
+ void writeDataClassExtension(Class klass) {
final String className = _className(options.prefix, klass.name);
indent.writeln('@interface $className ()');
indent.writeln('+ ($className *)fromMap:(NSDictionary *)dict;');
@@ -694,45 +837,69 @@
indent.writeln('@end');
}
- indent.writeln('');
-
- for (final Class klass in root.classes) {
+ void writeDataClassImplementation(Class klass) {
final String className = _className(options.prefix, klass.name);
- indent.writeln('@implementation $className');
- indent.write('+ ($className *)fromMap:(NSDictionary *)dict ');
- indent.scoped('{', '}', () {
- const String resultName = 'result';
- indent.writeln('$className *$resultName = [[$className alloc] init];');
- for (final NamedType field in klass.fields) {
- if (enumNames.contains(field.type.baseName)) {
- indent.writeln(
- '$resultName.${field.name} = [${_dictGetter(classNames, 'dict', field, options.prefix)} integerValue];');
- } else {
- indent.writeln(
- '$resultName.${field.name} = ${_dictGetter(classNames, 'dict', field, options.prefix)};');
- indent.write(
- 'if ((NSNull *)$resultName.${field.name} == [NSNull null]) ');
- indent.scoped('{', '}', () {
- indent.writeln('$resultName.${field.name} = nil;');
- });
+ void writeInitializer() {
+ _writeInitializerDeclaration(
+ indent, klass, root.classes, root.enums, options.prefix);
+ indent.writeScoped(' {', '}', () {
+ const String result = 'pigeonResult';
+ indent.writeln('$className* $result = [[$className alloc] init];');
+ for (final NamedType field in klass.fields) {
+ indent.writeln('$result.${field.name} = ${field.name};');
}
- }
- indent.writeln('return $resultName;');
- });
- indent.write('- (NSDictionary *)toMap ');
- indent.scoped('{', '}', () {
- indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:');
- for (final NamedType field in klass.fields) {
- indent.add(
- _dictValue(classNames, enumNames, field) + ', @"${field.name}", ');
- }
- indent.addln('nil];');
- });
+ indent.writeln('return $result;');
+ });
+ }
+
+ void writeFromMap() {
+ indent.write('+ ($className *)fromMap:(NSDictionary *)dict ');
+ indent.scoped('{', '}', () {
+ const String resultName = 'pigeonResult';
+ indent.writeln('$className *$resultName = [[$className alloc] init];');
+ for (final NamedType field in klass.fields) {
+ if (enumNames.contains(field.type.baseName)) {
+ indent.writeln(
+ '$resultName.${field.name} = [${_dictGetter(classNames, 'dict', field, options.prefix)} integerValue];');
+ } else {
+ indent.writeln(
+ '$resultName.${field.name} = ${_dictGetter(classNames, 'dict', field, options.prefix)};');
+ if (field.type.isNullable) {
+ indent.write(
+ 'if ((NSNull *)$resultName.${field.name} == [NSNull null]) ');
+ indent.scoped('{', '}', () {
+ indent.writeln('$resultName.${field.name} = nil;');
+ });
+ } else {
+ indent.writeln(
+ 'NSAssert((NSNull *)$resultName.${field.name} != [NSNull null], @"");');
+ }
+ }
+ }
+ indent.writeln('return $resultName;');
+ });
+ }
+
+ void writeToMap() {
+ indent.write('- (NSDictionary *)toMap ');
+ indent.scoped('{', '}', () {
+ indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:');
+ for (final NamedType field in klass.fields) {
+ indent.add(_dictValue(classNames, enumNames, field) +
+ ', @"${field.name}", ');
+ }
+ indent.addln('nil];');
+ });
+ }
+
+ indent.writeln('@implementation $className');
+ writeInitializer();
+ writeFromMap();
+ writeToMap();
indent.writeln('@end');
- indent.writeln('');
}
- for (final Api api in root.apis) {
+ void writeApi(Api api) {
final String codecName = _getCodecName(options.prefix, api.name);
_writeCodec(indent, codecName, options, api, root);
indent.addln('');
@@ -742,4 +909,19 @@
_writeFlutterApiSource(indent, options, api);
}
}
+
+ writeHeader();
+ writeImports();
+ indent.writeln('');
+ writeArcEnforcer();
+ indent.addln('');
+ writeWrapResultFunction();
+ indent.addln('');
+ root.classes.forEach(writeDataClassExtension);
+ indent.writeln('');
+ for (final Class klass in root.classes) {
+ writeDataClassImplementation(klass);
+ indent.writeln('');
+ }
+ root.apis.forEach(writeApi);
}
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index f78145b..dd31610 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -25,6 +25,7 @@
import 'ast.dart';
import 'ast_generator.dart';
import 'dart_generator.dart';
+import 'generator_tools.dart' as generator_tools;
import 'objc_generator.dart';
class _Asynchronous {
@@ -132,7 +133,8 @@
this.dartOptions,
this.copyrightHeader,
this.oneLanguage,
- this.astOut});
+ this.astOut,
+ this.debugGenerators});
/// Path to the file which will be processed.
final String? input;
@@ -170,6 +172,9 @@
/// Path to AST debugging output.
final String? astOut;
+ /// True means print out line number of generators in comments at newlines.
+ final bool? debugGenerators;
+
/// Creates a [PigeonOptions] from a Map representation where:
/// `x = PigeonOptions.fromMap(x.toMap())`.
static PigeonOptions fromMap(Map<String, Object> map) {
@@ -192,6 +197,7 @@
copyrightHeader: map['copyrightHeader'] as String?,
oneLanguage: map['oneLanguage'] as bool?,
astOut: map['astOut'] as String?,
+ debugGenerators: map['debugGenerators'] as bool?,
);
}
@@ -210,6 +216,8 @@
if (dartOptions != null) 'dartOptions': dartOptions!.toMap(),
if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
if (astOut != null) 'astOut': astOut!,
+ if (oneLanguage != null) 'oneLanguage': oneLanguage!,
+ if (debugGenerators != null) 'debugGenerators': debugGenerators!,
};
return result;
}
@@ -861,10 +869,23 @@
@override
Object? visitConstructorDeclaration(dart_ast.ConstructorDeclaration node) {
- final String type = _currentApi != null ? 'API classes' : 'data classes';
- _errors.add(Error(
- message: 'Constructors aren\'t supported in $type ("$node").',
- lineNumber: _calculateLineNumber(source, node.offset)));
+ if (_currentApi != null) {
+ _errors.add(Error(
+ message: 'Constructors aren\'t supported in API classes ("$node").',
+ lineNumber: _calculateLineNumber(source, node.offset)));
+ } else {
+ if (node.body.beginToken.lexeme != ';') {
+ _errors.add(Error(
+ message:
+ 'Constructor bodies aren\'t supported in data classes ("$node").',
+ lineNumber: _calculateLineNumber(source, node.offset)));
+ } else if (node.initializers.isNotEmpty) {
+ _errors.add(Error(
+ message:
+ 'Constructor initializers aren\'t supported in data classes (use "this.fieldName") ("$node").',
+ lineNumber: _calculateLineNumber(source, node.offset)));
+ }
+ }
node.visitChildren(this);
return null;
}
@@ -980,7 +1001,10 @@
defaultsTo: false)
..addOption('ast_out',
help:
- 'Path to generated AST debugging info. (Warning: format subject to change)');
+ 'Path to generated AST debugging info. (Warning: format subject to change)')
+ ..addFlag('debug_generators',
+ help: 'Print the line number of the generator in comments at newlines.',
+ defaultsTo: false);
/// Convert command-line arguments to [PigeonOptions].
static PigeonOptions parseArgs(List<String> args) {
@@ -1009,6 +1033,7 @@
copyrightHeader: results['copyright_header'],
oneLanguage: results['one_language'],
astOut: results['ast_out'],
+ debugGenerators: results['debug_generators'],
);
return opts;
}
@@ -1040,6 +1065,9 @@
{List<Generator>? generators, String? sdkPath}) async {
final Pigeon pigeon = Pigeon.setup();
PigeonOptions options = Pigeon.parseArgs(args);
+ if (options.debugGenerators ?? false) {
+ generator_tools.debugGenerators = true;
+ }
final List<Generator> safeGenerators = generators ??
<Generator>[
const DartGenerator(),
diff --git a/packages/pigeon/pigeons/non_null_fields.dart b/packages/pigeon/pigeons/non_null_fields.dart
new file mode 100644
index 0000000..bbce336
--- /dev/null
+++ b/packages/pigeon/pigeons/non_null_fields.dart
@@ -0,0 +1,30 @@
+// 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';
+
+class SearchRequest {
+ SearchRequest({required this.query});
+ String query;
+}
+
+class SearchReply {
+ SearchReply(this.result, this.error, this.indices);
+ String result;
+ String error;
+ List<int?> indices;
+}
+
+@HostApi()
+abstract class NonNullHostApi {
+ SearchReply search(SearchRequest nested);
+}
+
+@FlutterApi()
+abstract class NonNullFlutterApi {
+ SearchReply search(SearchRequest request);
+}
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java
new file mode 100644
index 0000000..3b1e150
--- /dev/null
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java
@@ -0,0 +1,24 @@
+// 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 com.example.android_unit_tests.NonNullFields.SearchRequest;
+import java.lang.IllegalStateException;
+import org.junit.Test;
+
+public class NonNullFieldsTest {
+ @Test
+ public void builder() {
+ SearchRequest request = new SearchRequest.Builder().setQuery("hello").build();
+ assertEquals(request.getQuery(), "hello");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void builderThrowsIfNull() {
+ SearchRequest request = new SearchRequest.Builder().build();
+ }
+}
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 f1b63b0..d71bddc 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.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
@@ -13,6 +13,22 @@
import 'package:flutter/services.dart';
class Everything {
+ Everything({
+ this.aBool,
+ this.anInt,
+ this.aDouble,
+ this.aString,
+ this.aByteArray,
+ this.a4ByteArray,
+ this.a8ByteArray,
+ this.aFloatArray,
+ this.aList,
+ this.aMap,
+ this.nestedList,
+ this.mapWithAnnotations,
+ this.mapWithObject,
+ });
+
bool? aBool;
int? anInt;
double? aDouble;
@@ -47,24 +63,25 @@
static Everything decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
- return Everything()
- ..aBool = pigeonMap['aBool'] as bool?
- ..anInt = pigeonMap['anInt'] as int?
- ..aDouble = pigeonMap['aDouble'] as double?
- ..aString = pigeonMap['aString'] as String?
- ..aByteArray = pigeonMap['aByteArray'] as Uint8List?
- ..a4ByteArray = pigeonMap['a4ByteArray'] as Int32List?
- ..a8ByteArray = pigeonMap['a8ByteArray'] as Int64List?
- ..aFloatArray = pigeonMap['aFloatArray'] as Float64List?
- ..aList = pigeonMap['aList'] as List<Object?>?
- ..aMap = pigeonMap['aMap'] as Map<Object?, Object?>?
- ..nestedList =
- (pigeonMap['nestedList'] as List<Object?>?)?.cast<List<bool?>?>()
- ..mapWithAnnotations =
+ return Everything(
+ aBool: pigeonMap['aBool'] as bool?,
+ anInt: pigeonMap['anInt'] as int?,
+ aDouble: pigeonMap['aDouble'] as double?,
+ aString: pigeonMap['aString'] as String?,
+ aByteArray: pigeonMap['aByteArray'] as Uint8List?,
+ a4ByteArray: pigeonMap['a4ByteArray'] as Int32List?,
+ a8ByteArray: pigeonMap['a8ByteArray'] as Int64List?,
+ aFloatArray: pigeonMap['aFloatArray'] as Float64List?,
+ aList: pigeonMap['aList'] as List<Object?>?,
+ aMap: pigeonMap['aMap'] as Map<Object?, Object?>?,
+ nestedList:
+ (pigeonMap['nestedList'] as List<Object?>?)?.cast<List<bool?>?>(),
+ mapWithAnnotations:
(pigeonMap['mapWithAnnotations'] as Map<Object?, Object?>?)
- ?.cast<String?, String?>()
- ..mapWithObject = (pigeonMap['mapWithObject'] as Map<Object?, Object?>?)
- ?.cast<String?, Object?>();
+ ?.cast<String?, String?>(),
+ mapWithObject: (pigeonMap['mapWithObject'] as Map<Object?, Object?>?)
+ ?.cast<String?, Object?>(),
+ );
}
}
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 1a8f6b6..6421b9f 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.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
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
new file mode 100644
index 0000000..c894284
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -0,0 +1,188 @@
+// 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.15), 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 SearchRequest {
+ SearchRequest({
+ required this.query,
+ });
+
+ String query;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['query'] = query;
+ return pigeonMap;
+ }
+
+ static SearchRequest decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return SearchRequest(
+ query: (pigeonMap['query'] as String?)!,
+ );
+ }
+}
+
+class SearchReply {
+ SearchReply({
+ required this.result,
+ required this.error,
+ required this.indices,
+ });
+
+ String result;
+ String error;
+ List<int?> indices;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['result'] = result;
+ pigeonMap['error'] = error;
+ pigeonMap['indices'] = indices;
+ return pigeonMap;
+ }
+
+ 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?)!,
+ indices: (pigeonMap['indices'] as List<Object?>?)!.cast<int?>(),
+ );
+ }
+}
+
+class _NonNullHostApiCodec extends StandardMessageCodec {
+ const _NonNullHostApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is SearchReply) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is SearchRequest) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return SearchReply.decode(readValue(buffer)!);
+
+ case 129:
+ return SearchRequest.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+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<SearchReply> search(SearchRequest arg_nested) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NonNullHostApi.search', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object>[arg_nested]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ details: null,
+ );
+ } 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 SearchReply?)!;
+ }
+ }
+}
+
+class _NonNullFlutterApiCodec extends StandardMessageCodec {
+ const _NonNullFlutterApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is SearchReply) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is SearchRequest) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return SearchReply.decode(readValue(buffer)!);
+
+ case 129:
+ return SearchRequest.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+abstract class NonNullFlutterApi {
+ static const MessageCodec<Object?> codec = _NonNullFlutterApiCodec();
+
+ SearchReply search(SearchRequest request);
+ static void setup(NonNullFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NonNullFlutterApi.search', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.NonNullFlutterApi.search was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final SearchRequest? arg_request = (args[0] as SearchRequest?);
+ assert(arg_request != null,
+ 'Argument for dev.flutter.pigeon.NonNullFlutterApi.search was null, expected non-null SearchRequest.');
+ final SearchReply output = api.search(arg_request!);
+ return output;
+ });
+ }
+ }
+ }
+}
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 b7e3f89..5b3c5c0 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.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
@@ -13,6 +13,10 @@
import 'package:flutter/services.dart';
class SearchRequest {
+ SearchRequest({
+ this.query,
+ });
+
String? query;
Object encode() {
@@ -23,11 +27,18 @@
static SearchRequest decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
- return SearchRequest()..query = pigeonMap['query'] as String?;
+ return SearchRequest(
+ query: pigeonMap['query'] as String?,
+ );
}
}
class SearchReply {
+ SearchReply({
+ this.result,
+ this.error,
+ });
+
String? result;
String? error;
@@ -40,13 +51,18 @@
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?;
+ return SearchReply(
+ result: pigeonMap['result'] as String?,
+ error: pigeonMap['error'] as String?,
+ );
}
}
class SearchRequests {
+ SearchRequests({
+ this.requests,
+ });
+
List<Object?>? requests;
Object encode() {
@@ -57,11 +73,17 @@
static SearchRequests decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
- return SearchRequests()..requests = pigeonMap['requests'] as List<Object?>?;
+ return SearchRequests(
+ requests: pigeonMap['requests'] as List<Object?>?,
+ );
}
}
class SearchReplies {
+ SearchReplies({
+ this.replies,
+ });
+
List<Object?>? replies;
Object encode() {
@@ -72,7 +94,9 @@
static SearchReplies decode(Object message) {
final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
- return SearchReplies()..replies = pigeonMap['replies'] as List<Object?>?;
+ return SearchReplies(
+ replies: pigeonMap['replies'] as List<Object?>?,
+ );
}
}
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 5259a06..780b412 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.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart
new file mode 100644
index 0000000..b4ef08c
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart
@@ -0,0 +1,13 @@
+// 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 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_unit_tests/non_null_fields.gen.dart';
+
+void main() {
+ test('test constructor', () {
+ final SearchRequest request = SearchRequest(query: 'what?');
+ expect(request.query, 'what?');
+ });
+}
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 6029534..0a81cb2 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
@@ -16,6 +16,8 @@
0DA5DFD626CC39D600D2354B /* multiple_arity.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFD526CC39D600D2354B /* multiple_arity.gen.m */; };
0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */; };
0DA5DFDB26CC3B3700D2354B /* HandlerBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */; };
+ 0DBD8C3E279B73F700E4FDBA /* NonNullFieldsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */; };
+ 0DBD8C41279B741800E4FDBA /* non_null_fields.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */; };
0DD2E6BA2684031300A7D764 /* void_arg_host.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6A62684031200A7D764 /* void_arg_host.gen.m */; };
0DD2E6BB2684031300A7D764 /* list.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6A72684031200A7D764 /* list.gen.m */; };
0DD2E6BC2684031300A7D764 /* host2flutter.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6AB2684031300A7D764 /* host2flutter.gen.m */; };
@@ -77,6 +79,9 @@
0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultipleArityTest.m; sourceTree = "<group>"; };
0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HandlerBinaryMessenger.h; sourceTree = "<group>"; };
0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HandlerBinaryMessenger.m; sourceTree = "<group>"; };
+ 0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NonNullFieldsTest.m; sourceTree = "<group>"; };
+ 0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = non_null_fields.gen.m; sourceTree = "<group>"; };
+ 0DBD8C40279B741800E4FDBA /* non_null_fields.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = non_null_fields.gen.h; sourceTree = "<group>"; };
0DD2E6A62684031200A7D764 /* void_arg_host.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = void_arg_host.gen.m; sourceTree = "<group>"; };
0DD2E6A72684031200A7D764 /* list.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = list.gen.m; sourceTree = "<group>"; };
0DD2E6A82684031200A7D764 /* host2flutter.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = host2flutter.gen.h; sourceTree = "<group>"; };
@@ -150,6 +155,7 @@
0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */,
0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */,
0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */,
+ 0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@@ -187,6 +193,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 0DBD8C40279B741800E4FDBA /* non_null_fields.gen.h */,
+ 0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */,
0DA5DFD426CC39D600D2354B /* multiple_arity.gen.h */,
0DA5DFD526CC39D600D2354B /* multiple_arity.gen.m */,
0D6FD3C326A76D400046D8BD /* primitive.gen.h */,
@@ -371,6 +379,7 @@
buildActionMask = 2147483647;
files = (
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */,
+ 0DBD8C3E279B73F700E4FDBA /* NonNullFieldsTest.m in Sources */,
0DF4E5C5266ECF4A00AEA855 /* AllDatatypesTest.m in Sources */,
0DF4E5C8266ED80900AEA855 /* EchoMessenger.m in Sources */,
0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */,
@@ -386,6 +395,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 0DBD8C41279B741800E4FDBA /* non_null_fields.gen.m in Sources */,
0DD2E6BD2684031300A7D764 /* async_handlers.gen.m in Sources */,
0D7A910D268E5D700056B5E1 /* all_void.gen.m in Sources */,
0DD2E6C12684031300A7D764 /* voidhost.gen.m in Sources */,
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m
new file mode 100644
index 0000000..7c28a08
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m
@@ -0,0 +1,23 @@
+
+// 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 <XCTest/XCTest.h>
+#import "EchoMessenger.h"
+#import "non_null_fields.gen.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NonNullFieldsTest : XCTestCase
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation NonNullFieldsTest
+
+- (void)testMake {
+ NNFSearchRequest* request = [NNFSearchRequest makeWithQuery:@"hello"];
+ XCTAssertEqualObjects(@"hello", request.query);
+}
+
+@end
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 04578e3..594479f 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.15 # This must match the version in lib/generator_tools.dart
+version: 1.0.16 # 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 d688bee..7bc2c9b 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -240,6 +240,7 @@
gen_ios_unittests_code ./pigeons/list.dart "LST"
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/primitive.dart ""
gen_ios_unittests_code ./pigeons/void_arg_flutter.dart "VAF"
gen_ios_unittests_code ./pigeons/void_arg_host.dart "VAH"
@@ -296,6 +297,7 @@
gen_android_unittests_code ./pigeons/list.dart PigeonList
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/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 0733fad..8a9dc51 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -199,7 +199,7 @@
expect(
code.replaceAll('\n', ' ').replaceAll(' ', ''),
contains(
- '..nested = pigeonMap[\'nested\'] != null ? Input.decode(pigeonMap[\'nested\']) : null;',
+ 'nested: pigeonMap[\'nested\'] != null ? Input.decode(pigeonMap[\'nested\']) : null',
),
);
});
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index ae815ef..f162d3c 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -31,7 +31,7 @@
final String code = sink.toString();
expect(code, contains('public class Messages'));
expect(code, contains('public static class Foobar'));
- expect(code, contains('private Long field1;'));
+ expect(code, contains('private @Nullable Long field1;'));
});
test('gen one enum', () {
@@ -200,14 +200,14 @@
const JavaOptions javaOptions = JavaOptions(className: 'Messages');
generateJava(javaOptions, root, sink);
final String code = sink.toString();
- expect(code, contains('private Boolean aBool;'));
- expect(code, contains('private Long aInt;'));
- expect(code, contains('private Double aDouble;'));
- expect(code, contains('private String aString;'));
- expect(code, contains('private byte[] aUint8List;'));
- expect(code, contains('private int[] aInt32List;'));
- expect(code, contains('private long[] aInt64List;'));
- expect(code, contains('private double[] aFloat64List;'));
+ expect(code, contains('private @Nullable Boolean aBool;'));
+ expect(code, contains('private @Nullable Long aInt;'));
+ expect(code, contains('private @Nullable Double aDouble;'));
+ expect(code, contains('private @Nullable String aString;'));
+ expect(code, contains('private @Nullable byte[] aUint8List;'));
+ expect(code, contains('private @Nullable int[] aInt32List;'));
+ expect(code, contains('private @Nullable long[] aInt64List;'));
+ expect(code, contains('private @Nullable double[] aFloat64List;'));
});
test('gen one flutter api', () {
@@ -408,7 +408,7 @@
generateJava(javaOptions, root, sink);
final String code = sink.toString();
expect(code, contains('public static class Foobar'));
- expect(code, contains('private List<Object> field1;'));
+ expect(code, contains('private @Nullable List<Object> field1;'));
});
test('gen map', () {
@@ -428,7 +428,7 @@
generateJava(javaOptions, root, sink);
final String code = sink.toString();
expect(code, contains('public static class Foobar'));
- expect(code, contains('private Map<Object, Object> field1;'));
+ expect(code, contains('private @Nullable Map<Object, Object> field1;'));
});
test('gen nested', () {
@@ -468,8 +468,8 @@
expect(code, contains('public class Messages'));
expect(code, contains('public static class Outer'));
expect(code, contains('public static class Nested'));
- expect(code, contains('private Nested nested;'));
- expect(code, contains('Nested.fromMap((Map)nested);'));
+ expect(code, contains('private @Nullable Nested nested;'));
+ expect(code, contains('Nested.fromMap((Map)nested)'));
expect(code,
contains('put("nested", (nested == null) ? null : nested.toMap());'));
});
@@ -616,7 +616,7 @@
expect(
code,
contains(
- 'fromMapResult.enum1 = enum1 == null ? null : Enum1.values()[(int)enum1];'));
+ 'pigeonResult.setEnum1(enum1 == null ? null : Enum1.values()[(int)enum1])'));
});
Iterable<String> _makeIterable(String string) sync* {
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index d1ddbc3..6ff4cce 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -110,7 +110,8 @@
final String code = sink.toString();
expect(code, contains('#import "foo.h"'));
expect(code, contains('@implementation Foobar'));
- expect(code, contains('result.enum1 = [dict[@"enum1"] integerValue];'));
+ expect(
+ code, contains('pigeonResult.enum1 = [dict[@"enum1"] integerValue];'));
});
test('gen one class header with enum', () {
@@ -307,7 +308,7 @@
generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink);
final String code = sink.toString();
expect(code, contains('@implementation Foobar'));
- expect(code, contains('result.aBool = dict[@"aBool"];'));
+ expect(code, contains('pigeonResult.aBool = dict[@"aBool"];'));
});
test('nested class header', () {
@@ -350,7 +351,8 @@
final StringBuffer sink = StringBuffer();
generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink);
final String code = sink.toString();
- expect(code, contains('result.nested = [Input fromMap:dict[@"nested"]];'));
+ expect(code,
+ contains('pigeonResult.nested = [Input fromMap:dict[@"nested"]];'));
expect(code, matches('[self.nested toMap].*@"nested"'));
});
@@ -1559,4 +1561,20 @@
expect(code, matches('divideValue:.*by:.*completion.*{'));
}
});
+
+ test('test non null field', () {
+ final Root root = Root(apis: <Api>[], classes: <Class>[
+ Class(name: 'Foobar', fields: <NamedType>[
+ NamedType(
+ type: const TypeDeclaration(baseName: 'String', isNullable: false),
+ name: 'field1',
+ offset: null)
+ ]),
+ ], enums: <Enum>[]);
+ final StringBuffer sink = StringBuffer();
+ generateObjcHeader(const ObjcOptions(), root, sink);
+ final String code = sink.toString();
+ expect(code, contains('@interface Foobar'));
+ expect(code, contains('@property(nonatomic, copy) NSString * field1'));
+ });
}
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 924087b..3ea099f 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -498,7 +498,23 @@
const String code = '''
class Foo {
int? x;
- Foo(this.x);
+ Foo({this.x});
+}
+
+@HostApi()
+abstract class Api {
+ Foo doit(Foo foo);
+}
+''';
+ final ParseResults results = _parseSource(code);
+ expect(results.errors.length, 0);
+ });
+
+ test('constructor body in data class', () {
+ const String code = '''
+class Foo {
+ int? x;
+ Foo({this.x}) { print('hi'); }
}
@HostApi()
@@ -512,6 +528,42 @@
expect(results.errors[0].message, contains('Constructor'));
});
+ test('constructor body in data class', () {
+ const String code = '''
+class Foo {
+ int? x;
+ Foo() : x = 0;
+}
+
+@HostApi()
+abstract class Api {
+ Foo doit(Foo foo);
+}
+''';
+ final ParseResults results = _parseSource(code);
+ expect(results.errors.length, 1);
+ expect(results.errors[0].lineNumber, 3);
+ expect(results.errors[0].message, contains('Constructor'));
+ });
+
+ test('constructor in api class', () {
+ const String code = '''
+class Foo {
+ int? x;
+}
+
+@HostApi()
+abstract class Api {
+ Api() { print('hi'); }
+ 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('Constructor'));
+ });
+
test('nullable api arguments', () {
const String code = '''
class Foo {