diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 9709fc886d..b4879df952 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0-experimental.6 + +* Added support for void return types. + ## 0.1.0-experimental.5 * Fixed runtime exception in Android with values of ints less than 2^32. diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index 8544907806..ca223cc5e1 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -23,6 +23,10 @@ void _writeHostApi(Indent indent, Api api) { indent.dec(); indent.dec(); indent.writeln(''); + final String returnStatement = + func.returnType == 'void' + ? '// noop' + : 'return ${func.returnType}._fromMap(replyMap[\'${Keys.result}\']);'; indent.format('''Map replyMap = await channel.send(requestMap); if (replyMap['error'] != null) { \tMap error = replyMap['${Keys.error}']; @@ -31,7 +35,7 @@ if (replyMap['error'] != null) { \t\t\tmessage: error['${Keys.errorMessage}'], \t\t\tdetails: error['${Keys.errorDetails}']); } else { -\treturn ${func.returnType}._fromMap(replyMap[\'${Keys.result}\']); +\t$returnStatement } '''); }); @@ -67,8 +71,13 @@ void _writeFlutterApi(Indent indent, Api api) { final String returnType = func.returnType; indent.writeln('Map mapMessage = message as Map;'); indent.writeln('$argType input = $argType._fromMap(mapMessage);'); - indent.writeln('$returnType output = api.${func.name}(input);'); - indent.writeln('return output._toMap();'); + final String call = 'api.${func.name}(input)'; + if (returnType == 'void') { + indent.writeln('$call;'); + } else { + indent.writeln('$returnType output = $call;'); + indent.writeln('return output._toMap();'); + } }); }); } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 8251cfd665..f064683939 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -4,10 +4,11 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:mirrors'; import 'ast.dart'; /// The current version of pigeon. -const String pigeonVersion = '0.1.0-experimental.5'; +const String pigeonVersion = '0.1.0-experimental.6'; /// Read all the content from [stdin] to a String. String readStdin() { @@ -154,3 +155,8 @@ class Keys { /// The key in an error hash for the 'details' value. static const String errorDetails = 'details'; } + +/// Returns true if `type` represents 'void'. +bool isVoid(TypeMirror type) { + return MirrorSystem.getName(type.simpleName) == 'void'; +} diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index 7850792398..0d227a0c54 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -66,10 +66,15 @@ void _writeHostApi(Indent indent, Api api) { 'HashMap wrapped = new HashMap();'); indent.write('try '); indent.scoped('{', '}', () { - indent - .writeln('$returnType output = api.${method.name}(input);'); - indent - .writeln('wrapped.put("${Keys.result}", output.toMap());'); + final String call = 'api.${method.name}(input)'; + if (method.returnType == 'void') { + indent.writeln('$call;'); + indent.writeln('wrapped.put("${Keys.result}", null);'); + } else { + indent.writeln('$returnType output = $call;'); + indent.writeln( + 'wrapped.put("${Keys.result}", output.toMap());'); + } }); indent.write('catch (Exception exception) '); indent.scoped('{', '}', () { @@ -102,8 +107,10 @@ void _writeFlutterApi(Indent indent, Api api) { }); for (Method func in api.methods) { final String channelName = makeChannelName(api, func); + final String returnType = + func.returnType == 'void' ? 'Void' : func.returnType; indent.write( - 'public void ${func.name}(${func.argType} argInput, Reply<${func.returnType}> callback) '); + 'public void ${func.name}(${func.argType} argInput, Reply<$returnType> callback) '); indent.scoped('{', '}', () { indent.writeln('BasicMessageChannel channel ='); indent.inc(); @@ -118,10 +125,14 @@ void _writeFlutterApi(Indent indent, Api api) { indent.scoped('{', '});', () { indent.write('public void reply(Object channelReply) '); indent.scoped('{', '}', () { - indent.writeln('HashMap outputMap = (HashMap)channelReply;'); - indent.writeln( - '${func.returnType} output = ${func.returnType}.fromMap(outputMap);'); - indent.writeln('callback.reply(output);'); + if (func.returnType == 'void') { + indent.writeln('callback.reply(null);'); + } else { + indent.writeln('HashMap outputMap = (HashMap)channelReply;'); + indent.writeln( + '${func.returnType} output = ${func.returnType}.fromMap(outputMap);'); + indent.writeln('callback.reply(output);'); + } }); }); }); diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 26185e4b75..9e38008257 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -26,6 +26,12 @@ String _className(String prefix, String className) { } } +String _callbackForType(String dartType, String objcType) { + return dartType == 'void' + ? 'void(^)(NSError*)' + : 'void(^)($objcType*, NSError*)'; +} + const Map _objcTypeForDartTypeMap = { 'bool': 'NSNumber *', 'int': 'NSNumber *', @@ -101,10 +107,13 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { if (api.location == ApiLocation.host) { indent.writeln('@protocol $apiName'); for (Method func in api.methods) { - final String returnType = _className(options.prefix, func.returnType); + final String returnTypeName = + _className(options.prefix, func.returnType); final String argType = _className(options.prefix, func.argType); + final String returnType = + func.returnType == 'void' ? 'void' : '$returnTypeName *'; indent.writeln( - '-($returnType *)${func.name}:($argType*)input error:(FlutterError **)error;'); + '-($returnType)${func.name}:($argType*)input error:(FlutterError **)error;'); } indent.writeln('@end'); indent.writeln(''); @@ -118,8 +127,9 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { for (Method func in api.methods) { final String returnType = _className(options.prefix, func.returnType); final String argType = _className(options.prefix, func.argType); + final String callbackType = _callbackForType(func.returnType, returnType); indent.writeln( - '- (void)${func.name}:($argType*)input completion:(void(^)($returnType*, NSError*))completion;'); + '- (void)${func.name}:($argType*)input completion:($callbackType)completion;'); } indent.writeln('@end'); } @@ -176,9 +186,14 @@ void _writeHostApiSource(Indent indent, ObjcOptions options, Api api) { _className(options.prefix, func.returnType); indent.writeln('$argType *input = [$argType fromMap:message];'); indent.writeln('FlutterError *error;'); - indent.writeln( - '$returnType *output = [api ${func.name}:input error:&error];'); - indent.writeln('callback(wrapResult([output toMap], error));'); + final String call = '[api ${func.name}:input error:&error]'; + if (func.returnType == 'void') { + indent.writeln('$call;'); + indent.writeln('callback(wrapResult(nil, error));'); + } else { + indent.writeln('$returnType *output = $call;'); + indent.writeln('callback(wrapResult([output toMap], error));'); + } }); }); indent.write('else '); @@ -213,8 +228,10 @@ void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) { for (Method func in api.methods) { final String returnType = _className(options.prefix, func.returnType); final String argType = _className(options.prefix, func.argType); + final String callbackType = _callbackForType(func.returnType, returnType); + indent.write( - '- (void)${func.name}:($argType*)input completion:(void(^)($returnType*, NSError*))completion '); + '- (void)${func.name}:($argType*)input completion:($callbackType)completion '); indent.scoped('{', '}', () { indent.writeln('FlutterBasicMessageChannel *channel ='); indent.inc(); @@ -227,10 +244,14 @@ void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) { indent.writeln('NSDictionary* inputMap = [input toMap];'); indent.write('[channel sendMessage:inputMap reply:^(id reply) '); indent.scoped('{', '}];', () { - indent.writeln('NSDictionary* outputMap = reply;'); - indent - .writeln('$returnType * output = [$returnType fromMap:outputMap];'); - indent.writeln('completion(output, nil);'); + if (func.returnType == 'void') { + indent.writeln('completion(nil);'); + } else { + indent.writeln('NSDictionary* outputMap = reply;'); + indent.writeln( + '$returnType * output = [$returnType fromMap:outputMap];'); + indent.writeln('completion(output, nil);'); + } }); }); } diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index a3e16c81dc..920c2cf22c 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -11,6 +11,7 @@ import 'package:pigeon/java_generator.dart'; import 'ast.dart'; import 'dart_generator.dart'; +import 'generator_tools.dart'; import 'objc_generator.dart'; const List _validTypes = [ @@ -152,7 +153,9 @@ class Pigeon { for (ClassMirror apiMirror in apis) { for (DeclarationMirror declaration in apiMirror.declarations.values) { if (declaration is MethodMirror && !declaration.isConstructor) { - classes.add(declaration.returnType); + if (!isVoid(declaration.returnType)) { + classes.add(declaration.returnType); + } classes.add(declaration.parameters[0].type); } } diff --git a/packages/pigeon/pigeons/voidflutter.dart b/packages/pigeon/pigeons/voidflutter.dart new file mode 100644 index 0000000000..ec0e93a4ce --- /dev/null +++ b/packages/pigeon/pigeons/voidflutter.dart @@ -0,0 +1,14 @@ +// Copyright 2020 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:pigeon/pigeon_lib.dart'; + +class SetRequest { + int value; +} + +@FlutterApi() +abstract class Api { + void setValue(SetRequest request); +} diff --git a/packages/pigeon/pigeons/voidhost.dart b/packages/pigeon/pigeons/voidhost.dart new file mode 100644 index 0000000000..b1eb5197c8 --- /dev/null +++ b/packages/pigeon/pigeons/voidhost.dart @@ -0,0 +1,14 @@ +// Copyright 2020 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:pigeon/pigeon_lib.dart'; + +class SetRequest { + int value; +} + +@HostApi() +abstract class Api { + void setValue(SetRequest request); +} diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 1ce025091f..32e001ad4e 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -1,5 +1,5 @@ name: pigeon -version: 0.1.0-experimental.5 +version: 0.1.0-experimental.6 description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. homepage: https://github.com/flutter/packages/tree/master/packages/pigeon dependencies: diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 14fbde3e6d..6da6551d81 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -81,4 +81,39 @@ void main() { expect(code, contains('abstract class Api')); expect(code, contains('void ApiSetup(Api')); }); + + test('host void', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.host, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateDart(root, sink); + final String code = sink.toString(); + expect(code, contains('Future doSomething')); + expect(code, contains('// noop')); + }); + + test('flutter void', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.flutter, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateDart(root, sink); + final String code = sink.toString(); + expect(code, isNot(matches('=.*doSomething'))); + expect(code, contains('doSomething(')); + expect(code, isNot(contains('._toMap()'))); + }); } diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index 6c51a70343..0c2f578869 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -121,4 +121,43 @@ void main() { expect(code, contains('public static class Api')); expect(code, matches('doSomething.*Input.*Output')); }); + + test('gen host void api', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.host, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + final JavaOptions javaOptions = JavaOptions(); + javaOptions.className = 'Messages'; + generateJava(javaOptions, root, sink); + final String code = sink.toString(); + expect(code, isNot(matches('=.*doSomething'))); + expect(code, contains('doSomething(')); + }); + + test('gen flutter void api', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.flutter, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + final JavaOptions javaOptions = JavaOptions(); + javaOptions.className = 'Messages'; + generateJava(javaOptions, root, sink); + final String code = sink.toString(); + expect(code, contains('Reply')); + expect(code, isNot(contains('.fromMap('))); + expect(code, contains('callback.reply(null)')); + }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index e19c3849c3..4cc89302e6 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -266,4 +266,71 @@ void main() { expect(code, contains('@implementation Api')); expect(code, matches('void.*doSomething.*Input.*Output.*{')); }); + + test('gen host void header', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.host, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink); + final String code = sink.toString(); + expect(code, contains('(void)doSomething')); + }); + + test('gen host void source', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.host, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink); + final String code = sink.toString(); + expect(code, isNot(matches('=.*doSomething'))); + expect(code, matches('[.*doSomething:.*]')); + expect(code, contains('callback(wrapResult(nil, error))')); + }); + + test('gen flutter void header', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.flutter, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateObjcHeader(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink); + final String code = sink.toString(); + expect(code, contains('completion:(void(^)(NSError*))')); + }); + + test('gen flutter void source', () { + final Root root = Root(apis: [ + Api(name: 'Api', location: ApiLocation.flutter, methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + ]); + final StringBuffer sink = StringBuffer(); + generateObjcSource(ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink); + final String code = sink.toString(); + expect(code, contains('completion:(void(^)(NSError*))')); + expect(code, contains('completion(nil)')); + }); } diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 8b2296f6c6..05ae811a8b 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -38,6 +38,11 @@ abstract class AFlutterApi { Output1 doit(Input1 input); } +@HostApi() +abstract class VoidApi { + void doit(Input1 input); +} + void main() { test('parse args - input', () { final PigeonOptions opts = @@ -145,4 +150,14 @@ void main() { expect(results.root.apis[0].name, equals('AFlutterApi')); expect(results.root.apis[0].location, equals(ApiLocation.flutter)); }); + + test('void host api', () { + final Pigeon pigeon = Pigeon.setup(); + final ParseResults results = pigeon.parse([VoidApi]); + expect(results.errors.length, equals(0)); + expect(results.root.apis.length, equals(1)); + expect(results.root.apis[0].methods.length, equals(1)); + expect(results.root.apis[0].name, equals('VoidApi')); + expect(results.root.apis[0].methods[0].returnType, equals('void')); + }); }