Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
43da42e
[pigeon] implemented generics
gaaclarke Jul 21, 2021
7d4f275
implemented generics for java
gaaclarke Jul 21, 2021
bd04fa8
implemented generics for objc
gaaclarke Jul 22, 2021
3e75a35
started parsing return value generics
gaaclarke Jul 22, 2021
5cebd68
started parsing return value generics
gaaclarke Jul 22, 2021
8e9de1d
started parsing argument generics
gaaclarke Jul 22, 2021
ceafca5
fixed maps on dart
gaaclarke Jul 22, 2021
d4e133a
formatter and started throwing an error for primitive generics
gaaclarke Jul 23, 2021
f4ab00d
fixed faulty rebase
gaaclarke Jul 28, 2021
e0819cf
stuarts feedback
gaaclarke Jul 29, 2021
6ea9b5b
stuarts feedback for typedeclaration
gaaclarke Jul 30, 2021
69744ac
Revert "stuarts feedback for typedeclaration"
gaaclarke Aug 3, 2021
9860126
introduced the TypedEntity and TypeDeclaration
gaaclarke Aug 3, 2021
845897d
Revert "introduced the TypedEntity and TypeDeclaration"
gaaclarke Aug 3, 2021
eea78a2
[pigeon] implemented multiple arity
gaaclarke Jul 23, 2021
9bdd2ef
removed 'void' references, took an empty list to mean void
gaaclarke Jul 24, 2021
f6dd824
added syntactic entity import
gaaclarke Aug 3, 2021
ad972ac
[pigeon] implemented generics support in primitives
gaaclarke Jul 26, 2021
27e1518
java support
gaaclarke Jul 26, 2021
96a9ec2
implemented objc
gaaclarke Jul 26, 2021
4adeb46
dart support for return types
gaaclarke Jul 26, 2021
30002a8
implemented java return values
gaaclarke Jul 27, 2021
0716337
implemented objc return types
gaaclarke Jul 27, 2021
fa1d5b5
formatter
gaaclarke Jul 27, 2021
f6ee67f
List -> List<Object>
gaaclarke Jul 27, 2021
482daa9
added generics to the primitive.dart pigeon
gaaclarke Jul 27, 2021
3a2f8bb
added better testing for dart
gaaclarke Jul 27, 2021
11c71d5
did some cleanup to factor out cast calls
gaaclarke Jul 27, 2021
15188a8
rebase fixes
gaaclarke Jul 29, 2021
505c3b4
fixed cast call to remove warning
gaaclarke Jul 30, 2021
0dd68e0
[pigeon] made generated dart code keep the parameter name
gaaclarke Jul 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [front-end] Added more errors for incorrect usage of Pigeon (previously they were just ignored).
* Moved Pigeon to using a custom codec which allows collection types to contain custom classes.
* Started allowing primitive data types as arguments and return types.
* Started supporting generics' type arguments for fields in classes.

## 0.3.0

Expand Down
64 changes: 49 additions & 15 deletions packages/pigeon/lib/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,19 @@ class Method extends Node {
Method({
required this.name,
required this.returnType,
required this.argType,
this.isArgNullable = false,
required this.arguments,
this.isAsynchronous = false,
this.isReturnNullable = false,
this.offset,
});

/// The name of the method.
String name;

/// The data-type of the return value.
String returnType;

/// True if the method can return a null value.
bool isReturnNullable;
Field returnType;

/// The data-type of the argument.
String argType;

/// True if the argument has a null tag `?`.
bool isArgNullable;
List<Field> arguments;

/// Whether the receiver of this method is expected to return synchronously or not.
bool isAsynchronous;
Expand All @@ -50,7 +42,7 @@ class Method extends Node {

@override
String toString() {
return '(Api name:$name returnType:$returnType argType:$argType isAsynchronous:$isAsynchronous)';
return '(Method name:$name returnType:$returnType argType:$arguments isAsynchronous:$isAsynchronous)';
}
}

Expand Down Expand Up @@ -82,8 +74,47 @@ class Api extends Node {
}
}

/// An entity that represents a typed concept, like a [TypeArgument] or [Field].
abstract class TypedEntity {
/// The data-type of the entity (ex 'String' or 'int').
String get dataType;

/// The type arguments to the entity.
List<TypeArgument>? get typeArguments;

/// True if the type is nullable.
bool get isNullable;
}

/// A parameter to a generic entity. For example, "String" to "List<String>".
class TypeArgument implements TypedEntity {
/// Constructor for [TypeArgument].
TypeArgument({
required this.dataType,
required this.isNullable,
this.typeArguments,
});

/// A string representation of the base datatype.
@override
final String dataType;

/// The type arguments to this [TypeArgument].
@override
final List<TypeArgument>? typeArguments;

/// True if the type is nullable.
@override
final bool isNullable;

@override
String toString() {
return '(TypeArgument dataType:$dataType isNullable:$isNullable typeArguments:$typeArguments)';
}
}

/// Represents a field on a [Class].
class Field extends Node {
class Field extends Node implements TypedEntity {
/// Parametric constructor for [Field].
Field({
required this.name,
Expand All @@ -97,20 +128,23 @@ class Field extends Node {
String name;

/// The data-type of the field (ex 'String' or 'int').
@override
String dataType;

/// The offset in the source file where the field appears.
int? offset;

/// True if the datatype is nullable (ex `int?`).
@override
bool isNullable;

/// Type parameters used for generics.
List<Field>? typeArguments;
@override
List<TypeArgument>? typeArguments;

@override
String toString() {
return '(Field name:$name dataType:$dataType)';
return '(Field name:$name dataType:$dataType typeArguments:$typeArguments)';
}
}

Expand Down
91 changes: 70 additions & 21 deletions packages/pigeon/lib/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ void _writeCodec(Indent indent, String codecName, Api api) {
});
}

String _makeGenericTypeArguments(TypedEntity entity, String nullTag) {
return entity.typeArguments != null
? '${entity.dataType}<${entity.typeArguments!.map((TypeArgument e) => 'Object$nullTag').reduce((String value, String element) => '$value, $element')}>'
: _addGenericTypes(entity, nullTag);
}

String _makeGenericCastCall(TypedEntity entity, String nullTag) {
return entity.typeArguments != null
? '.cast<${_flattenTypeArguments(entity.typeArguments!, nullTag)}>()'
: '';
}

String _getArgumentName(Field field) => field.name.isEmpty ? 'arg' : field.name;

void _writeHostApi(DartOptions opt, Indent indent, Api api) {
assert(api.location == ApiLocation.host);
final String codecName = _getCodecName(api);
Expand Down Expand Up @@ -121,12 +135,13 @@ final BinaryMessenger$nullTag _binaryMessenger;
}
String argSignature = '';
String sendArgument = 'null';
if (func.argType != 'void') {
argSignature = '${func.argType} arg';
sendArgument = 'arg';
if (func.arguments.isNotEmpty) {
sendArgument = _getArgumentName(func.arguments[0]);
argSignature =
'${_addGenericTypes(func.arguments[0], nullTag)} $sendArgument';
}
indent.write(
'Future<${func.returnType}> ${func.name}($argSignature) async ',
'Future<${_addGenericTypes(func.returnType, nullTag)}> ${func.name}($argSignature) async ',
);
indent.scoped('{', '}', () {
final String channelName = makeChannelName(api, func);
Expand All @@ -137,9 +152,12 @@ final BinaryMessenger$nullTag _binaryMessenger;
'\'$channelName\', codec, binaryMessenger: _binaryMessenger);',
);
});
final String returnStatement = func.returnType == 'void'
final String returnType =
_makeGenericTypeArguments(func.returnType, nullTag);
final String castCall = _makeGenericCastCall(func.returnType, nullTag);
final String returnStatement = func.returnType.dataType == 'void'
? '// noop'
: 'return (replyMap[\'${Keys.result}\'] as ${func.returnType}$nullTag)$unwrapOperator;';
: 'return (replyMap[\'${Keys.result}\'] as $returnType$nullTag)$unwrapOperator$castCall;';
indent.format('''
final Map<Object$nullTag, Object$nullTag>$nullTag replyMap =\n\t\tawait channel.send($sendArgument) as Map<Object$nullTag, Object$nullTag>$nullTag;
if (replyMap == null) {
Expand Down Expand Up @@ -181,10 +199,12 @@ void _writeFlutterApi(
indent.addln('');
for (final Method func in api.methods) {
final bool isAsync = func.isAsynchronous;
final String returnType =
isAsync ? 'Future<${func.returnType}>' : func.returnType;
final String argSignature =
func.argType == 'void' ? '' : '${func.argType} arg';
final String returnType = isAsync
? 'Future<${_addGenericTypes(func.returnType, nullTag)}>'
: _addGenericTypes(func.returnType, nullTag);
final String argSignature = func.arguments.isEmpty
? ''
: '${_addGenericTypes(func.arguments[0], nullTag)} ${_getArgumentName(func.arguments[0])}';
indent.writeln('$returnType ${func.name}($argSignature);');
}
indent.write('static void setup(${api.name}$nullTag api) ');
Expand Down Expand Up @@ -215,19 +235,21 @@ void _writeFlutterApi(
'channel.$messageHandlerSetter((Object$nullTag message) async ',
);
indent.scoped('{', '});', () {
final String argType = func.argType;
final String returnType = func.returnType;
final String returnType =
_addGenericTypes(func.returnType, nullTag);
final bool isAsync = func.isAsynchronous;
final String emptyReturnStatement = isMockHandler
? 'return <Object$nullTag, Object$nullTag>{};'
: func.returnType == 'void'
: func.returnType.dataType == 'void'
? 'return;'
: 'return null;';
String call;
if (argType == 'void') {
if (func.arguments.isEmpty) {
indent.writeln('// ignore message');
call = 'api.${func.name}()';
} else {
final String argType =
_addGenericTypes(func.arguments[0], nullTag);
indent.writeln(
'assert(message != null, \'Argument for $channelName was null. Expected $argType.\');',
);
Expand Down Expand Up @@ -263,17 +285,37 @@ void _writeFlutterApi(
});
}

String _addGenericTypes(String dataType, String nullTag) {
switch (dataType) {
/// Converts a [List] of [TypeArgument]s to a comma separated [String] to be
/// used in Dart code.
String _flattenTypeArguments(List<TypeArgument> args, String nullTag) {
return args
.map((TypeArgument arg) => arg.typeArguments == null
? '${arg.dataType}$nullTag'
: '${arg.dataType}<${_flattenTypeArguments(arg.typeArguments!, nullTag)}>$nullTag')
.reduce((String value, String element) => '$value, $element');
}

/// Creates the type declaration for use in Dart code from a [Field] making sure
/// that type arguments are used for primitive generic types.
String _addGenericTypes(TypedEntity entity, String nullTag) {
switch (entity.dataType) {
case 'List':
return 'List<Object$nullTag>$nullTag';
return (entity.typeArguments == null)
? 'List<Object$nullTag>'
: 'List<${_flattenTypeArguments(entity.typeArguments!, nullTag)}>';
case 'Map':
return 'Map<Object$nullTag, Object$nullTag>$nullTag';
return (entity.typeArguments == null)
? 'Map<Object$nullTag, Object$nullTag>'
: 'Map<${_flattenTypeArguments(entity.typeArguments!, nullTag)}>';
default:
return '$dataType$nullTag';
return entity.dataType;
}
}

String _addGenericTypesNullable(Field field, String nullTag) {
return '${_addGenericTypes(field, nullTag)}$nullTag';
}

/// Generates Dart source code for the given AST represented by [root],
/// outputting the code to [sink].
void generateDart(DartOptions opt, Root root, StringSink sink) {
Expand Down Expand Up @@ -315,7 +357,7 @@ void generateDart(DartOptions opt, Root root, StringSink sink) {
indent.write('class ${klass.name} ');
indent.scoped('{', '}', () {
for (final Field field in klass.fields) {
final String datatype = _addGenericTypes(field.dataType, nullTag);
final String datatype = _addGenericTypesNullable(field, nullTag);
indent.writeln('$datatype ${field.name};');
}
if (klass.fields.isNotEmpty) {
Expand Down Expand Up @@ -365,9 +407,16 @@ pigeonMap['${field.name}'] != null
pigeonMap['${field.name}'] != null
\t\t? ${field.dataType}.values[pigeonMap['${field.name}']$unwrapOperator as int]
\t\t: null''', leadingSpace: false, trailingNewline: false);
} else if (field.typeArguments != null) {
final String genericType =
_makeGenericTypeArguments(field, nullTag);
final String castCall = _makeGenericCastCall(field, nullTag);
indent.add(
'(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$nullTag$castCall',
);
} else {
indent.add(
'pigeonMap[\'${field.name}\'] as ${_addGenericTypes(field.dataType, nullTag)}',
'pigeonMap[\'${field.name}\'] as ${_addGenericTypesNullable(field, nullTag)}',
);
}
indent.addln(index == klass.fields.length - 1 ? ';' : '');
Expand Down
10 changes: 6 additions & 4 deletions packages/pigeon/lib/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ class HostDatatype {
/// `builtinResolver` will return the host datatype for the Dart datatype for
/// builtin types. `customResolver` can modify the datatype of custom types.
HostDatatype getHostDatatype(Field field, List<Class> classes, List<Enum> enums,
String? Function(String) builtinResolver,
String? Function(Field) builtinResolver,
{String Function(String)? customResolver}) {
final String? datatype = builtinResolver(field.dataType);
final String? datatype = builtinResolver(field);
if (datatype == null) {
if (classes.map((Class x) => x.name).contains(field.dataType)) {
final String customName = customResolver != null
Expand Down Expand Up @@ -285,8 +285,10 @@ const int _minimumCodecFieldKey = 128;
Iterable<EnumeratedClass> getCodecClasses(Api api) sync* {
final Set<String> names = <String>{};
for (final Method method in api.methods) {
names.add(method.returnType);
names.add(method.argType);
names.add(method.returnType.dataType);
if (method.arguments.isNotEmpty) {
names.add(method.arguments[0].dataType);
}
}
final List<String> sortedNames = names
.where((String element) =>
Expand Down
Loading