[Pigeon] added support for void return types (#121)

This commit is contained in:
gaaclarke
2020-03-23 10:18:08 -07:00
committed by GitHub
parent 5cbb85023b
commit d410cebbf0
13 changed files with 264 additions and 26 deletions

View File

@ -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.

View File

@ -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);');
final String call = 'api.${func.name}(input)';
if (returnType == 'void') {
indent.writeln('$call;');
} else {
indent.writeln('$returnType output = $call;');
indent.writeln('return output._toMap();');
}
});
});
}

View File

@ -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';
}

View File

@ -66,10 +66,15 @@ void _writeHostApi(Indent indent, Api api) {
'HashMap<String, HashMap> wrapped = new HashMap<String, 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<Object> channel =');
indent.inc();
@ -118,10 +125,14 @@ void _writeFlutterApi(Indent indent, Api api) {
indent.scoped('{', '});', () {
indent.write('public void reply(Object channelReply) ');
indent.scoped('{', '}', () {
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);');
}
});
});
});

View File

@ -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<String, String> _objcTypeForDartTypeMap = <String, String>{
'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];');
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('{', '}];', () {
if (func.returnType == 'void') {
indent.writeln('completion(nil);');
} else {
indent.writeln('NSDictionary* outputMap = reply;');
indent
.writeln('$returnType * output = [$returnType fromMap:outputMap];');
indent.writeln(
'$returnType * output = [$returnType fromMap:outputMap];');
indent.writeln('completion(output, nil);');
}
});
});
}

View File

@ -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<String> _validTypes = <String>[
@ -152,7 +153,9 @@ class Pigeon {
for (ClassMirror apiMirror in apis) {
for (DeclarationMirror declaration in apiMirror.declarations.values) {
if (declaration is MethodMirror && !declaration.isConstructor) {
if (!isVoid(declaration.returnType)) {
classes.add(declaration.returnType);
}
classes.add(declaration.parameters[0].type);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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:

View File

@ -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>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[Field(name: 'input', dataType: 'String')]),
]);
final StringBuffer sink = StringBuffer();
generateDart(root, sink);
final String code = sink.toString();
expect(code, contains('Future<void> doSomething'));
expect(code, contains('// noop'));
});
test('flutter void', () {
final Root root = Root(apis: <Api>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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()')));
});
}

View File

@ -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>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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<Void>'));
expect(code, isNot(contains('.fromMap(')));
expect(code, contains('callback.reply(null)'));
});
}

View File

@ -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>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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>[
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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>[
Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
Method(name: 'doSomething', argType: 'Input', returnType: 'void')
])
], classes: <Class>[
Class(
name: 'Input',
fields: <Field>[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)'));
});
}

View File

@ -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(<Type>[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'));
});
}