From 6e385f12be56cbc83092aa036efaff03a9c02bb0 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:23:58 -0500 Subject: [PATCH 01/15] update ast --- packages/pigeon/CHANGELOG.md | 4 + packages/pigeon/lib/ast.dart | 213 ++++++- packages/pigeon/lib/cpp_generator.dart | 35 +- packages/pigeon/lib/dart_generator.dart | 22 +- packages/pigeon/lib/generator.dart | 105 +++- packages/pigeon/lib/generator_tools.dart | 100 ++++ packages/pigeon/lib/java_generator.dart | 20 +- packages/pigeon/lib/kotlin_generator.dart | 19 +- packages/pigeon/lib/objc_generator.dart | 16 +- packages/pigeon/lib/pigeon_lib.dart | 560 +++++++++++++++++- packages/pigeon/lib/swift_generator.dart | 20 +- packages/pigeon/pubspec.yaml | 2 +- packages/pigeon/test/cpp_generator_test.dart | 88 ++- packages/pigeon/test/dart_generator_test.dart | 170 +++--- .../pigeon/test/generator_tools_test.dart | 27 +- packages/pigeon/test/java_generator_test.dart | 87 ++- .../pigeon/test/kotlin_generator_test.dart | 96 +-- packages/pigeon/test/objc_generator_test.dart | 193 ++++-- packages/pigeon/test/pigeon_lib_test.dart | 8 +- .../pigeon/test/swift_generator_test.dart | 84 ++- 20 files changed, 1481 insertions(+), 388 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 6e05728aa341..a0dbe6f8c8a3 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 16.0.1 + +* Adds ProxyApi to AST generation. + ## 16.0.0 * [java] Adds `VoidResult` type for `Void` returns. diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 9d79a781afbd..7b0e32511fae 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -29,7 +29,10 @@ class Method extends Node { required this.name, required this.returnType, required this.parameters, + required this.location, + this.required = false, this.isAsynchronous = false, + this.isStatic = false, this.offset, this.objcSelector = '', this.swiftFunction = '', @@ -68,6 +71,18 @@ class Method extends Node { /// For example: [" List of documentation comments, separated by line.", ...] List documentationComments; + /// Where the implementation of this method is located, host or Flutter. + ApiLocation location; + + /// Whether this method is required to be implemented. + /// + /// This flag is typically used to determine whether a callback method for + /// a `ProxyApi` is nullable or not. + bool required; + + /// Whether this is a static method of a ProxyApi. + bool isStatic; + @override String toString() { final String objcSelectorStr = @@ -78,29 +93,185 @@ class Method extends Node { } } -/// Represents a collection of [Method]s that are hosted on a given [location]. -class Api extends Node { +/// Represents a collection of [Method]s that are implemented on the platform +/// side. +class AstHostApi extends Api { + /// Parametric constructor for [AstHostApi]. + AstHostApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + this.dartHostTestHandler, + }); + + /// The name of the Dart test interface to generate to help with testing. + String? dartHostTestHandler; + + @override + String toString() { + return '(HostApi name:$name methods:$methods documentationComments:$documentationComments dartHostTestHandler:$dartHostTestHandler)'; + } +} + +/// Represents a collection of [Method]s that are hosted on the Flutter side. +class AstFlutterApi extends Api { + /// Parametric constructor for [AstFlutterApi]. + AstFlutterApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + }); + + @override + String toString() { + return '(FlutterApi name:$name methods:$methods documentationComments:$documentationComments)'; + } +} + +/// Represents an API that wraps a native class. +class AstProxyApi extends Api { + /// Parametric constructor for [AstProxyApi]. + AstProxyApi({ + required super.name, + required super.methods, + super.documentationComments = const [], + required this.constructors, + required this.fields, + this.superClassName, + this.interfacesNames = const {}, + }); + + /// List of constructors inside the API. + final List constructors; + + /// List of fields inside the API. + List fields; + + /// Name of the class this class considers the super class. + final String? superClassName; + + /// Name of the classes this class considers to be implemented. + final Set interfacesNames; + + /// Methods implemented in the host platform language. + Iterable get hostMethods => methods.where( + (Method method) => method.location == ApiLocation.host, + ); + + /// Methods implemented in Flutter. + Iterable get flutterMethods => methods.where( + (Method method) => method.location == ApiLocation.flutter, + ); + + /// All fields that are attached. + /// + /// See [attached]. + Iterable get attachedFields => fields.where( + (Field field) => field.isAttached, + ); + + /// All fields that are not attached. + /// + /// See [attached]. + Iterable get unattachedFields => fields.where( + (Field field) => !field.isAttached, + ); + + @override + String toString() { + return '(ProxyApi name:$name methods:$methods documentationComments:$documentationComments superClassName:$superClassName interfacesNames:$interfacesNames)'; + } +} + +/// Represents a constructor for an API. +class Constructor extends Node { + /// Parametric constructor for [Constructor]. + Constructor({ + required this.name, + required this.parameters, + this.offset, + this.swiftFunction = '', + this.documentationComments = const [], + }); + + /// The name of the method. + String name; + + /// The parameters passed into the [Constructor]. + List parameters; + + /// The offset in the source file where the field appears. + int? offset; + + /// An override for the generated swift function signature (ex. "divideNumber(_:by:)"). + String swiftFunction; + + /// List of documentation comments, separated by line. + /// + /// Lines should not include the comment marker itself, but should include any + /// leading whitespace, so that any indentation in the original comment is preserved. + /// For example: [" List of documentation comments, separated by line.", ...] + List documentationComments; + + @override + String toString() { + final String swiftFunctionStr = + swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction'; + return '(Constructor name:$name parameters:$parameters $swiftFunctionStr documentationComments:$documentationComments)'; + } +} + +/// Represents a field of an API. +class Field extends NamedType { + /// Constructor for [Field]. + Field({ + required super.name, + required super.type, + super.offset, + super.documentationComments, + this.isAttached = false, + this.isStatic = false, + }) : assert(!isStatic || isAttached); + + /// Whether this is an attached field for a [AstProxyApi]. + /// + /// See [attached]. + final bool isAttached; + + /// Whether this is a static field of a [AstProxyApi]. + /// + /// A static field must also be attached. See [attached]. + final bool isStatic; + + /// Returns a copy of [Parameter] instance with new attached [TypeDeclaration]. + @override + Field copyWithType(TypeDeclaration type) { + return Field( + name: name, + type: type, + offset: offset, + documentationComments: documentationComments, + isAttached: isAttached, + isStatic: isStatic, + ); + } +} + +/// Represents a collection of [Method]s. +sealed class Api extends Node { /// Parametric constructor for [Api]. Api({ required this.name, - required this.location, required this.methods, - this.dartHostTestHandler, this.documentationComments = const [], }); /// The name of the API. String name; - /// Where the API's implementation is located, host or Flutter. - ApiLocation location; - /// List of methods inside the API. List methods; - /// The name of the Dart test interface to generate to help with testing. - String? dartHostTestHandler; - /// List of documentation comments, separated by line. /// /// Lines should not include the comment marker itself, but should include any @@ -110,7 +281,7 @@ class Api extends Node { @override String toString() { - return '(Api name:$name location:$location methods:$methods documentationComments:$documentationComments)'; + return '(Api name:$name methods:$methods documentationComments:$documentationComments)'; } } @@ -123,6 +294,7 @@ class TypeDeclaration { required this.isNullable, this.associatedEnum, this.associatedClass, + this.associatedProxyApi, this.typeArguments = const [], }); @@ -132,6 +304,7 @@ class TypeDeclaration { isNullable = false, associatedEnum = null, associatedClass = null, + associatedProxyApi = null, typeArguments = const []; /// The base name of the [TypeDeclaration] (ex 'Foo' to 'Foo?'). @@ -158,6 +331,12 @@ class TypeDeclaration { /// Associated [Class], if any. final Class? associatedClass; + /// Associated [AstProxyApi], if any. + final AstProxyApi? associatedProxyApi; + + /// Whether the [TypeDeclaration] has an [associatedProxyApi]. + bool get isProxyApi => associatedProxyApi != null; + @override int get hashCode { // This has to be implemented because TypeDeclaration is used as a Key to a @@ -207,11 +386,21 @@ class TypeDeclaration { ); } + /// Returns duplicated `TypeDeclaration` with attached `associatedProxyApi` value. + TypeDeclaration copyWithProxyApi(AstProxyApi proxyApiDefinition) { + return TypeDeclaration( + baseName: baseName, + isNullable: isNullable, + associatedProxyApi: proxyApiDefinition, + typeArguments: typeArguments, + ); + } + @override String toString() { final String typeArgumentsStr = typeArguments.isEmpty ? '' : 'typeArguments:$typeArguments'; - return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass)'; + return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass isProxyApi:$isProxyApi)'; } } diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 5247f424052e..2ec8ccd00833 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -175,7 +175,7 @@ class CppHeaderGenerator extends StructuredGenerator { indent, anEnum.documentationComments, _docCommentSpec); indent.write('enum class ${anEnum.name} '); indent.addScoped('{', '};', () { - enumerate(anEnum.members, (int index, final EnumMember member) { + enumerate(anEnum.members, (int index, EnumMember member) { addDocumentationComments( indent, member.documentationComments, _docCommentSpec); indent.writeln( @@ -191,14 +191,21 @@ class CppHeaderGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); _writeFlutterError(indent); if (hasHostApi) { - _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); + _writeErrorOr( + indent, + friends: root.apis + .where((Api api) => api is AstFlutterApi || api is AstHostApi) + .map((Api api) => api.name), + ); } if (hasFlutterApi) { // Nothing yet. @@ -291,7 +298,8 @@ class CppHeaderGenerator extends StructuredGenerator { indent.writeln('friend class ${friend.name};'); } } - for (final Api api in root.apis) { + for (final Api api in root.apis + .where((Api api) => api is AstFlutterApi || api is AstHostApi)) { // TODO(gaaclarke): Find a way to be more precise with our // friendships. indent.writeln('friend class ${api.name};'); @@ -317,10 +325,9 @@ class CppHeaderGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -370,10 +377,9 @@ class CppHeaderGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -778,6 +784,7 @@ class CppSourceGenerator extends StructuredGenerator { final Iterable<_IndexedField> indexedFields = indexMap( getFieldsInSerializationOrder(classDefinition), (int index, NamedType field) => _IndexedField(index, field)); + final Iterable<_IndexedField> nullableFields = indexedFields .where((_IndexedField field) => field.field.type.isNullable); final Iterable<_IndexedField> nonNullableFields = indexedFields @@ -823,10 +830,9 @@ class CppSourceGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } @@ -937,10 +943,9 @@ class CppSourceGenerator extends StructuredGenerator { CppOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(generatorOptions, root, indent, api); } diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index 52e2d0e68d95..b8601458e2dc 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -312,12 +312,11 @@ $resultAt != null DartOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { String Function(Method)? channelNameFunc, bool isMockHandler = false, required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); String codecName = _standardMessageCodec; if (getCodecClasses(api, root).isNotEmpty) { codecName = _getCodecName(api); @@ -488,10 +487,9 @@ $resultAt != null DartOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); String codecName = _standardMessageCodec; if (getCodecClasses(api, root).isNotEmpty) { codecName = _getCodecName(api); @@ -640,13 +638,11 @@ if (${_varNamePrefix}replyList == null) { relativeDartPath.replaceFirst(RegExp(r'^.*/lib/'), ''); indent.writeln("import 'package:$dartOutputPackageName/$path';"); } - for (final Api api in root.apis) { - if (api.location == ApiLocation.host && api.dartHostTestHandler != null) { - final Api mockApi = Api( + for (final AstHostApi api in root.apis.whereType()) { + if (api.dartHostTestHandler != null) { + final AstFlutterApi mockApi = AstFlutterApi( name: api.dartHostTestHandler!, methods: api.methods, - location: ApiLocation.flutter, - dartHostTestHandler: api.dartHostTestHandler, documentationComments: api.documentationComments, ); writeFlutterApi( @@ -696,10 +692,10 @@ if (${_varNamePrefix}replyList == null) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = + root.apis.any((Api api) => api.methods.isNotEmpty && api is AstHostApi); + final bool hasFlutterApi = root.apis + .any((Api api) => api.methods.isNotEmpty && api is AstFlutterApi); if (hasHostApi) { _writeCreateConnectionError(indent); diff --git a/packages/pigeon/lib/generator.dart b/packages/pigeon/lib/generator.dart index 1bd083078745..3711fd28dbe3 100644 --- a/packages/pigeon/lib/generator.dart +++ b/packages/pigeon/lib/generator.dart @@ -63,6 +63,24 @@ abstract class StructuredGenerator extends Generator { dartPackageName: dartPackageName, ); + if (root.apis.any((Api api) => api is AstProxyApi)) { + writeInstanceManager( + generatorOptions, + root, + indent, + dartPackageName: dartPackageName, + ); + + writeInstanceManagerApi( + generatorOptions, + root, + indent, + dartPackageName: dartPackageName, + ); + + writeProxyApiBaseCodec(generatorOptions, root, indent); + } + writeEnums( generatorOptions, root, @@ -224,22 +242,31 @@ abstract class StructuredGenerator extends Generator { required String dartPackageName, }) { for (final Api api in root.apis) { - if (api.location == ApiLocation.host) { - writeHostApi( - generatorOptions, - root, - indent, - api, - dartPackageName: dartPackageName, - ); - } else if (api.location == ApiLocation.flutter) { - writeFlutterApi( - generatorOptions, - root, - indent, - api, - dartPackageName: dartPackageName, - ); + switch (api) { + case AstHostApi(): + writeHostApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); + case AstFlutterApi(): + writeFlutterApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); + case AstProxyApi(): + writeProxyApi( + generatorOptions, + root, + indent, + api, + dartPackageName: dartPackageName, + ); } } } @@ -249,7 +276,7 @@ abstract class StructuredGenerator extends Generator { T generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }); @@ -258,7 +285,49 @@ abstract class StructuredGenerator extends Generator { T generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }); + + /// Writes the implementation of an `InstanceManager` to [indent]. + void writeInstanceManager( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) {} + + /// Writes the implementation of the API for the `InstanceManager` to + /// [indent]. + void writeInstanceManagerApi( + T generatorOptions, + Root root, + Indent indent, { + required String dartPackageName, + }) {} + + /// Writes the base codec to be used by all ProxyApis. + /// + /// This codec should use `128` as the identifier for objects that exist in + /// an `InstanceManager`. The write implementation should convert an instance + /// to an identifier. The read implementation should covert the identifier + /// to an instance. + /// + /// This will serve as the default codec for all ProxyApis. If a ProxyApi + /// needs to create its own codec (it has methods/fields/constructor that use + /// a data class) it should extend this codec and not `StandardMessageCodec`. + void writeProxyApiBaseCodec( + T generatorOptions, + Root root, + Indent indent, + ) {} + + /// Writes a single Proxy Api to [indent]. + void writeProxyApi( + T generatorOptions, + Root root, + Indent indent, + AstProxyApi api, { + required String dartPackageName, + }) {} } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index ba32f720a2a0..4fba3835620e 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -282,6 +282,24 @@ String getGeneratedCodeWarning() { /// String to be printed after `getGeneratedCodeWarning()'s warning`. const String seeAlsoWarning = 'See also: https://pub.dev/packages/pigeon'; +/// Prefix for utility classes generated for ProxyApis. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String classNamePrefix = 'Pigeon_'; + +/// Name for the generated InstanceManager for ProxyApis. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String instanceManagerClassName = '${classNamePrefix}InstanceManager'; + +/// Prefix for class member names not defined by the user. +/// +/// This lowers the chances of variable name collisions with user defined +/// parameters. +const String classMemberNamePrefix = 'pigeon_'; + /// Collection of keys used in dictionaries across generators. class Keys { /// The key in the result hash for the 'result' value. @@ -588,6 +606,88 @@ String? deducePackageName(String mainDartFile) { } } +/// Recursively search for all the interfaces apis from a list of names of +/// interfaces. +/// +/// This method assumes that all interfaces names can be found in +/// [allProxyApis]. Otherwise, throws an [ArgumentError]. +Set recursiveFindAllInterfacesApis( + AstProxyApi api, + Iterable allProxyApis, +) { + final Set interfacesApis = {}; + + for (final AstProxyApi proxyApi in allProxyApis) { + if (api.interfacesNames.contains(proxyApi.name)) { + interfacesApis.add(proxyApi); + } + } + + if (interfacesApis.length != api.interfacesNames.length) { + throw ArgumentError( + 'Could not find a ProxyApi for every interface name: ' + '${api.interfacesNames}, ${allProxyApis.map((Api api) => api.name)}', + ); + } + + for (final AstProxyApi proxyApi in Set.from(interfacesApis)) { + interfacesApis.addAll( + recursiveFindAllInterfacesApis(proxyApi, allProxyApis), + ); + } + + return interfacesApis; +} + +/// Creates a list of ProxyApis where each `extends` the ProxyApi that follows +/// it. +/// +/// Returns an empty list if [proxyApi] does not extend a ProxyApi. +/// +/// This method assumes the super classes of each ProxyApi doesn't create a +/// loop. Throws a [ArgumentError] if a loop is found. +/// +/// This method also assumes that all super class names can be found in +/// [allProxyApis]. Otherwise, throws an [ArgumentError]. +List recursiveGetSuperClassApisChain( + AstProxyApi proxyApi, + Iterable allProxyApis, +) { + final List proxyApis = []; + + String? currentProxyApiName = proxyApi.superClassName; + while (currentProxyApiName != null) { + if (proxyApis.length > allProxyApis.length) { + final Iterable apiNames = proxyApis.map( + (AstProxyApi api) => api.name, + ); + throw ArgumentError( + 'Loop found when processing super classes for a ProxyApi: ' + '${proxyApi.name},${apiNames.join(',')}', + ); + } + + AstProxyApi? nextProxyApi; + for (final AstProxyApi node in allProxyApis) { + if (currentProxyApiName == node.name) { + nextProxyApi = node; + proxyApis.add(node); + } + } + + if (nextProxyApi == null) { + throw ArgumentError( + 'Could not find a ProxyApi for every super class name: ' + '$currentProxyApiName, ${allProxyApis.map((Api api) => api.name)}', + ); + } + + currentProxyApiName = nextProxyApi.superClassName; + } + + return proxyApis; +} + /// Enum to specify api type when generating code. enum ApiType { /// Flutter api. diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart index 43b42c40e909..0758071e9435 100644 --- a/packages/pigeon/lib/java_generator.dart +++ b/packages/pigeon/lib/java_generator.dart @@ -400,7 +400,7 @@ class JavaGenerator extends StructuredGenerator { JavaOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { /// Returns an argument name that can be used in a context where it is possible to collide @@ -414,7 +414,6 @@ class JavaGenerator extends StructuredGenerator { return '${_getArgumentName(count, argument)}Arg'; } - assert(api.location == ApiLocation.flutter); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(indent, api, root); } @@ -556,9 +555,9 @@ class JavaGenerator extends StructuredGenerator { required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous) || - api.location == ApiLocation.flutter)) { + api is AstFlutterApi)) { indent.newln(); _writeResultInterfaces(indent); } @@ -577,10 +576,9 @@ class JavaGenerator extends StructuredGenerator { JavaOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); if (getCodecClasses(api, root).isNotEmpty) { _writeCodec(indent, api, root); } @@ -979,10 +977,12 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); indent.newln(); _writeErrorClass(indent); diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart index 88ffb487406e..78337cc2cb14 100644 --- a/packages/pigeon/lib/kotlin_generator.dart +++ b/packages/pigeon/lib/kotlin_generator.dart @@ -307,7 +307,7 @@ class KotlinGenerator extends StructuredGenerator { required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous))) { indent.newln(); } @@ -325,10 +325,9 @@ class KotlinGenerator extends StructuredGenerator { KotlinOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; if (isCustomCodec) { _writeCodec(indent, api, root); @@ -448,11 +447,9 @@ class KotlinGenerator extends StructuredGenerator { KotlinOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); - final String apiName = api.name; final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; @@ -742,10 +739,12 @@ class KotlinGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapResult(indent); diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 903957d083c2..bc26495e650b 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -617,10 +617,9 @@ class ObjcSourceGenerator extends StructuredGenerator { ObjcOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); final String apiName = _className(generatorOptions.prefix, api.name); _writeCodecAndGetter(generatorOptions, root, indent, api); @@ -649,10 +648,9 @@ class ObjcSourceGenerator extends StructuredGenerator { ObjcOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); final String apiName = _className(generatorOptions.prefix, api.name); _writeCodecAndGetter(generatorOptions, root, indent, api); @@ -702,10 +700,12 @@ class ObjcSourceGenerator extends StructuredGenerator { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapError(indent); diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index e114af93caa4..2b3bda5f7ca1 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -39,9 +39,44 @@ class _Asynchronous { const _Asynchronous(); } +class _Attached { + const _Attached(); +} + +class _Static { + const _Static(); +} + /// Metadata to annotate a Api method as asynchronous const Object async = _Asynchronous(); +/// Metadata to annotate the field of a ProxyApi as an Attached Field. +/// +/// Attached fields provide a synchronous [AstProxyApi] instance as a field +/// for another [AstProxyApi]. +/// +/// Attached fields: +/// * Must be nonnull. +/// * Must be a ProxyApi type. +/// * Must be a ProxyApi that contains any fields. +/// * Must be a ProxyApi that does not have a required Flutter method. +/// +/// Example generated code: +/// +/// ```dart +/// class MyProxyApi { +/// final MyOtherProxyApi myField = __pigeon_myField(). +/// } +/// ``` +/// +/// The field provides access to the value synchronously, but the native +/// instance is stored in the native `InstanceManager` asynchronously. Similar +/// to how constructors are implemented. +const Object attached = _Attached(); + +/// Metadata to annotate an method or an Attached Field of a ProxyApi as static. +const Object static = _Static(); + /// Metadata annotation used to configure how Pigeon will generate code. class ConfigurePigeon { /// Constructor for ConfigurePigeon. @@ -88,6 +123,30 @@ class FlutterApi { const FlutterApi(); } +/// Metadata to annotate a Pigeon API that wraps a native class. +/// +/// The abstract class with this annotation groups a collection of Dart↔host +/// constructors, fields, methods and host↔Dart methods used to wrap a native +/// class. +/// +/// The generated Dart class acts as a proxy to a native type and +/// maintains instances automatically with an `InstanceManager`. The generated +/// host language class implements methods to interact with class instances +/// or static methods. +class ProxyApi { + /// Parametric constructor for [ProxyApi]. + const ProxyApi({this.superClass}); + + /// The proxy api that is a super class to this one. + /// + /// This provides an alternative to calling `extends` on a class since this + /// requires calling the super class constructor. + /// + /// Note that using this instead of `extends` can cause unexpected conflicts + /// with inherited method names. + final Type? superClass; +} + /// Metadata to annotation methods to control the selector used for objc output. /// The number of components in the provided selector must match the number of /// arguments in the annotated method. @@ -787,7 +846,16 @@ List _validateAst(Root root, String source) { } } } + for (final Api api in root.apis) { + if (api is AstProxyApi) { + result.addAll(_validateProxyApi( + api, + source, + customClasses: customClasses.toSet(), + proxyApis: root.apis.whereType().toSet(), + )); + } for (final Method method in api.methods) { for (final Parameter param in method.parameters) { if (param.type.baseName.isEmpty) { @@ -809,7 +877,7 @@ List _validateAst(Root root, String source) { lineNumber: _calculateLineNumberNullable(source, param.offset), )); } - if (api.location == ApiLocation.flutter) { + if (api is AstFlutterApi) { if (!param.isPositional) { result.add(Error( message: @@ -847,7 +915,7 @@ List _validateAst(Root root, String source) { } } if (method.taskQueueType != TaskQueueType.serial && - api.location != ApiLocation.host) { + method.location == ApiLocation.flutter) { result.add(Error( message: 'Unsupported TaskQueue specification on ${method.name}', lineNumber: _calculateLineNumberNullable(source, method.offset), @@ -859,6 +927,275 @@ List _validateAst(Root root, String source) { return result; } +List _validateProxyApi( + AstProxyApi api, + String source, { + required Set customClasses, + required Set proxyApis, +}) { + final List result = []; + + bool isDataClass(NamedType type) => + customClasses.contains(type.type.baseName); + bool isProxyApi(NamedType type) => proxyApis.any( + (AstProxyApi api) => api.name == type.type.baseName, + ); + Error unsupportedDataClassError(NamedType type) { + return Error( + message: 'ProxyApis do not support data classes: ${type.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, type.offset), + ); + } + + // Validate super class is another ProxyApi + final String? superClassName = api.superClassName; + if (api.superClassName != null && + !proxyApis.any((AstProxyApi api) => api.name == superClassName)) { + result.add(Error( + message: + 'Super class of ${api.name} is not marked as a @ProxyApi: $superClassName', + )); + } + + // Validate all interfaces are other ProxyApis + for (final String interfaceName in api.interfacesNames) { + if (!proxyApis.any((AstProxyApi api) => api.name == interfaceName)) { + result.add(Error( + message: + 'Interface of ${api.name} is not marked as a @ProxyApi: $interfaceName', + )); + } + } + + List? superClassChain; + try { + superClassChain = recursiveGetSuperClassApisChain(api, proxyApis); + } catch (error) { + result.add(Error(message: error.toString())); + } + + // Validate that the api does not inherit a non attached field from its super class. + if (superClassChain != null && + superClassChain.isNotEmpty && + superClassChain.first.unattachedFields.isNotEmpty) { + result.add(Error( + message: + 'Non attached fields can not be inherited. Non attached field found for parent class ${api.unattachedFields.first.name}', + lineNumber: _calculateLineNumberNullable( + source, + api.unattachedFields.first.offset, + ), + )); + } + + for (final AstProxyApi proxyApi in proxyApis) { + // Validate this api is not used as an attached field while either: + // 1. Having an unattached field. + // 2. Having a required Flutter method. + final bool hasUnattachedField = api.unattachedFields.isNotEmpty; + final bool hasRequiredFlutterMethod = + api.flutterMethods.any((Method method) => method.required); + if (hasUnattachedField || hasRequiredFlutterMethod) { + for (final Field field in proxyApi.attachedFields) { + if (field.type.baseName == api.name) { + if (hasUnattachedField) { + result.add(Error( + message: + 'ProxyApis with fields can not be used as attached fields: ${field.name}', + lineNumber: _calculateLineNumberNullable( + source, + field.offset, + ), + )); + } + if (hasRequiredFlutterMethod) { + result.add(Error( + message: + 'ProxyApis with required callback methods can not be used as attached fields: ${field.name}', + lineNumber: _calculateLineNumberNullable( + source, + field.offset, + ), + )); + } + } + } + } + + // Validate this api isn't used as an interface and contains anything except + // Flutter methods. + final bool isValidInterfaceProxyApi = api.hostMethods.isEmpty && + api.constructors.isEmpty && + api.fields.isEmpty; + if (!isValidInterfaceProxyApi) { + for (final String interfaceName in proxyApi.interfacesNames) { + if (interfaceName == api.name) { + result.add(Error( + message: + 'ProxyApis used as interfaces can only have callback methods: ${proxyApi.name}', + )); + } + } + } + } + + // Validate constructor parameters + for (final Constructor constructor in api.constructors) { + for (final Parameter parameter in constructor.parameters) { + if (isDataClass(parameter)) { + result.add(unsupportedDataClassError(parameter)); + } + + if (api.fields.any((Field field) => field.name == parameter.name) || + api.flutterMethods + .any((Method method) => method.name == parameter.name)) { + result.add(Error( + message: + 'Parameter names must not share a name with a field or callback method in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + + if (parameter.type.baseName.isEmpty) { + result.add(Error( + message: + 'Parameters must specify their type in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else if (parameter.name.startsWith('__pigeon_')) { + result.add(Error( + message: + 'Parameter name must not begin with "__pigeon_" in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else if (parameter.name == 'pigeonChannelCodec') { + result.add(Error( + message: + 'Parameter name must not be "pigeonChannelCodec" in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else if (parameter.name.startsWith(classNamePrefix)) { + result.add(Error( + message: + 'Parameter name must not begin with "$classNamePrefix" in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else if (parameter.name.startsWith(classMemberNamePrefix)) { + result.add(Error( + message: + 'Parameter name must not begin with "$classMemberNamePrefix" in constructor "${constructor.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + } + if (constructor.swiftFunction.isNotEmpty) { + final RegExp signatureRegex = + RegExp('\\w+ *\\((\\w+:){${constructor.parameters.length}}\\)'); + if (!signatureRegex.hasMatch(constructor.swiftFunction)) { + result.add(Error( + message: + 'Invalid constructor signature, expected ${constructor.parameters.length} parameters.', + lineNumber: _calculateLineNumberNullable(source, constructor.offset), + )); + } + } + } + + // Validate method parameters + for (final Method method in api.methods) { + for (final Parameter parameter in method.parameters) { + if (isDataClass(parameter)) { + result.add(unsupportedDataClassError(parameter)); + } + + if (parameter.name.startsWith(classNamePrefix)) { + result.add(Error( + message: + 'Parameter name must not begin with "$classNamePrefix" in method "${method.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } else if (parameter.name.startsWith(classMemberNamePrefix)) { + result.add(Error( + message: + 'Parameter name must not begin with "$classMemberNamePrefix" in method "${method.name}" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } + } + + if (method.location == ApiLocation.flutter && method.isStatic) { + result.add(Error( + message: 'Static callback methods are not supported: ${method.name}.', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); + } + } + + // Validate fields + for (final Field field in api.fields) { + if (isDataClass(field)) { + result.add(unsupportedDataClassError(field)); + } else if (field.isStatic) { + if (!isProxyApi(field)) { + result.add(Error( + message: + 'Static fields are considered attached fields and must be a ProxyApi: ${field.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } else if (field.type.isNullable) { + result.add(Error( + message: + 'Static fields are considered attached fields and must not be nullable: ${field.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } else if (field.isAttached) { + if (!isProxyApi(field)) { + result.add(Error( + message: + 'Attached fields must be a ProxyApi: ${field.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + if (field.type.isNullable) { + result.add(Error( + message: + 'Attached fields must not be nullable: ${field.type.baseName}.', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } + + if (field.name.startsWith('__pigeon_')) { + result.add(Error( + message: + 'Field name must not begin with "__pigeon_" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } else if (field.name == 'pigeonChannelCodec') { + result.add(Error( + message: + 'Field name must not be "pigeonChannelCodec" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } else if (field.name.startsWith(classNamePrefix)) { + result.add(Error( + message: + 'Field name must not begin with "$classNamePrefix" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } else if (field.name.startsWith(classMemberNamePrefix)) { + result.add(Error( + message: + 'Field name must not begin with "$classMemberNamePrefix" in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, field.offset), + )); + } + } + + return result; +} + class _FindInitializer extends dart_ast_visitor.RecursiveAstVisitor { dart_ast.Expression? initializer; @override @@ -927,6 +1264,10 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { !referencedEnums .map((Enum e) => e.name) .contains(element.key.baseName) && + !_apis + .whereType() + .map((AstProxyApi e) => e.name) + .contains(element.key.baseName) && !validTypes.contains(element.key.baseName) && !element.key.isVoid && element.key.baseName != 'dynamic' && @@ -943,7 +1284,9 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { for (final Class classDefinition in referencedClasses) { final List fields = []; for (final NamedType field in classDefinition.fields) { - fields.add(field.copyWithType(_attachClassesAndEnums(field.type))); + fields.add(field.copyWithType( + _attachClassesEnumsAndProxyApis(field.type), + )); } classDefinition.fields = fields; } @@ -952,10 +1295,31 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { for (final Method func in api.methods) { final List paramList = []; for (final Parameter param in func.parameters) { - paramList.add(param.copyWithType(_attachClassesAndEnums(param.type))); + paramList.add(param.copyWithType( + _attachClassesEnumsAndProxyApis(param.type), + )); } func.parameters = paramList; - func.returnType = _attachClassesAndEnums(func.returnType); + func.returnType = _attachClassesEnumsAndProxyApis(func.returnType); + } + if (api is AstProxyApi) { + for (final Constructor constructor in api.constructors) { + final List paramList = []; + for (final Parameter param in constructor.parameters) { + paramList.add( + param.copyWithType(_attachClassesEnumsAndProxyApis(param.type)), + ); + } + constructor.parameters = paramList; + } + + final List fieldList = []; + for (final Field field in api.fields) { + fieldList.add(field.copyWithType( + _attachClassesEnumsAndProxyApis(field.type), + )); + } + api.fields = fieldList; } } @@ -968,15 +1332,21 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { ); } - TypeDeclaration _attachClassesAndEnums(TypeDeclaration type) { + TypeDeclaration _attachClassesEnumsAndProxyApis(TypeDeclaration type) { final Enum? assocEnum = _enums.firstWhereOrNull( (Enum enumDefinition) => enumDefinition.name == type.baseName); final Class? assocClass = _classes.firstWhereOrNull( (Class classDefinition) => classDefinition.name == type.baseName); + final AstProxyApi? assocProxyApi = + _apis.whereType().firstWhereOrNull( + (Api apiDefinition) => apiDefinition.name == type.baseName, + ); if (assocClass != null) { return type.copyWithClass(assocClass); } else if (assocEnum != null) { return type.copyWithEnum(assocEnum); + } else if (assocProxyApi != null) { + return type.copyWithProxyApi(assocProxyApi); } return type; } @@ -1003,6 +1373,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { return expression.value!; } else if (expression is dart_ast.BooleanLiteral) { return expression.value; + } else if (expression is dart_ast.SimpleIdentifier) { + return expression.name; } else if (expression is dart_ast.ListLiteral) { final List list = []; for (final dart_ast.CollectionElement element in expression.elements) { @@ -1016,6 +1388,19 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } return list; + } else if (expression is dart_ast.SetOrMapLiteral) { + final Set set = {}; + for (final dart_ast.CollectionElement element in expression.elements) { + if (element is dart_ast.Expression) { + set.add(_expressionToMap(element)); + } else { + _errors.add(Error( + message: 'expected Expression but found $element', + lineNumber: _calculateLineNumber(source, element.offset), + )); + } + } + return set; } else { _errors.add(Error( message: @@ -1081,19 +1466,57 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } } - _currentApi = Api( + + _currentApi = AstHostApi( name: node.name.lexeme, - location: ApiLocation.host, methods: [], dartHostTestHandler: dartHostTestHandler, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); } else if (_hasMetadata(node.metadata, 'FlutterApi')) { - _currentApi = Api( + _currentApi = AstFlutterApi( + name: node.name.lexeme, + methods: [], + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ); + } else if (_hasMetadata(node.metadata, 'ProxyApi')) { + final dart_ast.Annotation proxyApiAnnotation = node.metadata.firstWhere( + (dart_ast.Annotation element) => element.name.name == 'ProxyApi', + ); + + final Map annotationMap = {}; + for (final dart_ast.Expression expression + in proxyApiAnnotation.arguments!.arguments) { + if (expression is dart_ast.NamedExpression) { + annotationMap[expression.name.label.name] = + _expressionToMap(expression.expression); + } + } + + final String? superClassName = annotationMap['superClass'] as String?; + if (superClassName != null && node.extendsClause != null) { + _errors.add( + Error( + message: + 'ProxyApis should either set the super class in the annotation OR use extends: ("${node.name.lexeme}").', + lineNumber: _calculateLineNumber(source, node.offset), + ), + ); + } + + _currentApi = AstProxyApi( name: node.name.lexeme, - location: ApiLocation.flutter, methods: [], + constructors: [], + fields: [], + superClassName: + superClassName ?? node.extendsClause?.superclass.name2.lexeme, + interfacesNames: node.implementsClause?.interfaces + .map((dart_ast.NamedType type) => type.name2.lexeme) + .toSet() ?? + {}, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); @@ -1204,6 +1627,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List arguments = parameters.parameters.map(formalParameterToPigeonParameter).toList(); final bool isAsynchronous = _hasMetadata(node.metadata, 'async'); + final bool isStatic = _hasMetadata(node.metadata, 'static'); final String objcSelector = _findMetadata(node.metadata, 'ObjCSelector') ?.arguments ?.arguments @@ -1243,6 +1667,13 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { typeAnnotationsToTypeArguments(returnType.typeArguments), isNullable: returnType.question != null), parameters: arguments, + required: true, + isStatic: isStatic, + location: switch (_currentApi!) { + AstHostApi() => ApiLocation.host, + AstProxyApi() => ApiLocation.host, + AstFlutterApi() => ApiLocation.flutter, + }, isAsynchronous: isAsynchronous, objcSelector: objcSelector, swiftFunction: swiftFunction, @@ -1298,8 +1729,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { @override Object? visitFieldDeclaration(dart_ast.FieldDeclaration node) { + final dart_ast.TypeAnnotation? type = node.fields.type; if (_currentClass != null) { - final dart_ast.TypeAnnotation? type = node.fields.type; if (node.isStatic) { _errors.add(Error( message: @@ -1335,6 +1766,88 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { message: 'Expected a named type but found "$node".', lineNumber: _calculateLineNumber(source, node.offset))); } + } else if (_currentApi is AstProxyApi) { + final bool isStatic = _hasMetadata(node.metadata, 'static'); + if (type is dart_ast.GenericFunctionType) { + final List parameters = type.parameters.parameters + .map(formalParameterToPigeonParameter) + .toList(); + final String swiftFunction = + _findMetadata(node.metadata, 'SwiftFunction') + ?.arguments + ?.arguments + .first + .asNullable() + ?.value ?? + ''; + final dart_ast.ArgumentList? taskQueueArguments = + _findMetadata(node.metadata, 'TaskQueue')?.arguments; + final String? taskQueueTypeName = taskQueueArguments == null + ? null + : getFirstChildOfType(taskQueueArguments) + ?.expression + .asNullable() + ?.name; + final TaskQueueType taskQueueType = + _stringToEnum(TaskQueueType.values, taskQueueTypeName) ?? + TaskQueueType.serial; + + // Methods without named return types aren't supported. + final dart_ast.TypeAnnotation returnType = type.returnType!; + returnType as dart_ast.NamedType; + + _currentApi!.methods.add( + Method( + name: node.fields.variables[0].name.lexeme, + returnType: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(returnType), + typeArguments: + typeAnnotationsToTypeArguments(returnType.typeArguments), + isNullable: returnType.question != null, + ), + location: ApiLocation.flutter, + required: type.question == null, + isStatic: isStatic, + parameters: parameters, + isAsynchronous: _hasMetadata(node.metadata, 'async'), + swiftFunction: swiftFunction, + offset: node.offset, + taskQueueType: taskQueueType, + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ), + ); + } else if (type is dart_ast.NamedType) { + final _FindInitializer findInitializerVisitor = _FindInitializer(); + node.visitChildren(findInitializerVisitor); + if (findInitializerVisitor.initializer != null) { + _errors.add(Error( + message: + 'Initialization isn\'t supported for fields in ProxyApis ("$node"), just use nullable types with no initializer (example "int? x;").', + lineNumber: _calculateLineNumber(source, node.offset))); + } else { + final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; + (_currentApi as AstProxyApi?)!.fields.add( + Field( + type: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(type), + isNullable: type.question != null, + typeArguments: typeAnnotationsToTypeArguments( + typeArguments, + ), + ), + name: node.fields.variables[0].name.lexeme, + isAttached: + _hasMetadata(node.metadata, 'attached') || isStatic, + isStatic: isStatic, + offset: node.offset, + documentationComments: _documentationCommentsParser( + node.documentationComment?.tokens, + ), + ), + ); + } + } } else if (_currentApi != null) { _errors.add(Error( message: 'Fields aren\'t supported in Pigeon API classes ("$node").', @@ -1346,7 +1859,30 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { @override Object? visitConstructorDeclaration(dart_ast.ConstructorDeclaration node) { - if (_currentApi != null) { + if (_currentApi is AstProxyApi) { + final dart_ast.FormalParameterList parameters = node.parameters; + final List arguments = + parameters.parameters.map(formalParameterToPigeonParameter).toList(); + final String swiftFunction = _findMetadata(node.metadata, 'SwiftFunction') + ?.arguments + ?.arguments + .first + .asNullable() + ?.value ?? + ''; + + (_currentApi as AstProxyApi?)!.constructors.add( + Constructor( + name: node.name?.lexeme ?? '', + parameters: arguments, + swiftFunction: swiftFunction, + offset: node.offset, + documentationComments: _documentationCommentsParser( + node.documentationComment?.tokens, + ), + ), + ); + } else if (_currentApi != null) { _errors.add(Error( message: 'Constructors aren\'t supported in API classes ("$node").', lineNumber: _calculateLineNumber(source, node.offset))); diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart index ae869426e620..cd2b36880530 100644 --- a/packages/pigeon/lib/swift_generator.dart +++ b/packages/pigeon/lib/swift_generator.dart @@ -236,7 +236,7 @@ import FlutterMacOS required String dartPackageName, }) { if (root.apis.any((Api api) => - api.location == ApiLocation.host && + api is AstHostApi && api.methods.any((Method it) => it.isAsynchronous))) { indent.newln(); } @@ -256,11 +256,9 @@ import FlutterMacOS SwiftOptions generatorOptions, Root root, Indent indent, - Api api, { + AstFlutterApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.flutter); - /// Returns an argument name that can be used in a context where it is possible to collide. String getEnumSafeArgumentExpression( Root root, int count, NamedType argument) { @@ -380,11 +378,9 @@ import FlutterMacOS SwiftOptions generatorOptions, Root root, Indent indent, - Api api, { + AstHostApi api, { required String dartPackageName, }) { - assert(api.location == ApiLocation.host); - final String apiName = api.name; final bool isCustomCodec = getCodecClasses(api, root).isNotEmpty; @@ -802,10 +798,12 @@ private func nilOrValue(_ value: Any?) -> T? { Indent indent, { required String dartPackageName, }) { - final bool hasHostApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.host); - final bool hasFlutterApi = root.apis.any((Api api) => - api.methods.isNotEmpty && api.location == ApiLocation.flutter); + final bool hasHostApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); + final bool hasFlutterApi = root.apis + .whereType() + .any((Api api) => api.methods.isNotEmpty); if (hasHostApi) { _writeWrapResult(indent); diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index c820df9256f8..865a4ed151d7 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,7 +2,7 @@ name: pigeon 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%3A%22p%3A+pigeon%22 -version: 16.0.0 # This must match the version in lib/generator_tools.dart +version: 16.0.1 # This must match the version in lib/generator_tools.dart environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index cbd290b42fa3..af7865815d3a 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -25,7 +25,7 @@ final Enum emptyEnum = Enum( void main() { test('gen one api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', parameters: [ @@ -37,6 +37,8 @@ void main() { ), name: 'input') ], + required: true, + location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', isNullable: false, @@ -101,7 +103,7 @@ void main() { test('naming follows style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', parameters: [ @@ -113,6 +115,7 @@ void main() { ), name: 'someInput') ], + location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', isNullable: false, @@ -179,9 +182,10 @@ void main() { test('FlutterError fields are private with public accessors', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -230,9 +234,10 @@ void main() { test('Error field is private with public accessors', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -273,9 +278,10 @@ void main() { test('Spaces before {', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -350,9 +356,10 @@ void main() { test('include blocks follow style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -423,9 +430,10 @@ void main() { test('namespaces follows style', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -478,9 +486,10 @@ void main() { test('data classes handle nullable fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -661,9 +670,10 @@ void main() { test('data classes handle non-nullable fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -804,9 +814,10 @@ void main() { test('host nullable return types map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'returnNullableBool', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'bool', @@ -815,6 +826,7 @@ void main() { ), Method( name: 'returnNullableInt', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'int', @@ -823,6 +835,7 @@ void main() { ), Method( name: 'returnNullableString', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'String', @@ -831,6 +844,7 @@ void main() { ), Method( name: 'returnNullableList', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'List', @@ -845,6 +859,7 @@ void main() { ), Method( name: 'returnNullableMap', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'Map', @@ -863,6 +878,7 @@ void main() { ), Method( name: 'returnNullableDataClass', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'ReturnData', @@ -921,9 +937,10 @@ void main() { test('host non-nullable return types map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'returnBool', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'bool', @@ -932,6 +949,7 @@ void main() { ), Method( name: 'returnInt', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'int', @@ -940,6 +958,7 @@ void main() { ), Method( name: 'returnString', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'String', @@ -948,6 +967,7 @@ void main() { ), Method( name: 'returnList', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'List', @@ -962,6 +982,7 @@ void main() { ), Method( name: 'returnMap', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration( baseName: 'Map', @@ -980,6 +1001,7 @@ void main() { ), Method( name: 'returnDataClass', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'ReturnData', @@ -1024,9 +1046,10 @@ void main() { test('host nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1179,9 +1202,10 @@ void main() { test('host non-nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1329,9 +1353,10 @@ void main() { test('flutter nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'aBool', @@ -1488,9 +1513,10 @@ void main() { test('flutter non-nullable arguments map correctly', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'aBool', @@ -1621,9 +1647,10 @@ void main() { test('host API argument extraction uses references', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'anArg', @@ -1661,9 +1688,10 @@ void main() { test('enum argument', () { final Root root = Root( apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1705,13 +1733,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1785,12 +1813,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1827,9 +1855,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1884,9 +1913,10 @@ void main() { test('Does not send unwrapped EncodableLists', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( name: 'aBool', @@ -1966,15 +1996,17 @@ void main() { test('does not keep unowned references in async handlers', () { final Root root = Root(apis: [ - Api(name: 'HostApi', location: ApiLocation.host, methods: [ + AstHostApi(name: 'HostApi', methods: [ Method( name: 'noop', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true, ), Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1988,15 +2020,17 @@ void main() { isAsynchronous: true, ), ]), - Api(name: 'FlutterApi', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'FlutterApi', methods: [ Method( name: 'noop', + location: ApiLocation.flutter, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true, ), Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: const TypeDeclaration( @@ -2038,12 +2072,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 83a6556c7fab..884dabf5f683 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -85,9 +85,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -137,9 +138,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -170,9 +172,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -305,9 +308,10 @@ void main() { test('flutterApi', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -358,9 +362,10 @@ void main() { test('host void', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -398,9 +403,10 @@ void main() { test('flutter void return', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -441,9 +447,10 @@ void main() { test('flutter void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -477,9 +484,10 @@ void main() { test('flutter enum argument with enum class', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -531,9 +539,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -567,9 +576,10 @@ void main() { test('flutter non-nullable enum argument with enum class', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -620,9 +630,10 @@ void main() { test('host void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -655,42 +666,40 @@ void main() { test('mock dart handler', () { final Root root = Root(apis: [ - Api( - name: 'Api', + AstHostApi(name: 'Api', dartHostTestHandler: 'ApiMock', methods: [ + Method( + name: 'doSomething', location: ApiLocation.host, - dartHostTestHandler: 'ApiMock', - methods: [ - Method( - name: 'doSomething', - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Input', - isNullable: false, - associatedClass: emptyClass, - ), - name: '') - ], - returnType: TypeDeclaration( - baseName: 'Output', - isNullable: false, - associatedClass: emptyClass, - ), - ), - Method( - name: 'voidReturner', - parameters: [ - Parameter( - type: TypeDeclaration( - baseName: 'Input', - isNullable: false, - associatedClass: emptyClass, - ), - name: '') - ], - returnType: const TypeDeclaration.voidDeclaration(), - ) - ]) + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + isNullable: false, + associatedClass: emptyClass, + ), + name: '') + ], + returnType: TypeDeclaration( + baseName: 'Output', + isNullable: false, + associatedClass: emptyClass, + ), + ), + Method( + name: 'voidReturner', + location: ApiLocation.host, + parameters: [ + Parameter( + type: TypeDeclaration( + baseName: 'Input', + isNullable: false, + associatedClass: emptyClass, + ), + name: '') + ], + returnType: const TypeDeclaration.voidDeclaration(), + ) + ]) ], classes: [ Class(name: 'Input', fields: [ NamedType( @@ -748,9 +757,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -803,9 +813,10 @@ void main() { test('gen one async Flutter Api with void return', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -853,9 +864,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -906,9 +918,10 @@ void main() { test('async host void argument', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1027,9 +1040,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1061,9 +1075,10 @@ void main() { test('flutter generics argument with void return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1095,9 +1110,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1129,9 +1145,10 @@ void main() { test('flutter generics argument non void return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1173,9 +1190,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1202,9 +1220,10 @@ void main() { test('return nullable collection host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: true, @@ -1236,9 +1255,10 @@ void main() { test('return nullable async host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1266,9 +1286,10 @@ void main() { test('return nullable flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1295,9 +1316,10 @@ void main() { test('return nullable async flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1325,9 +1347,10 @@ void main() { test('platform error for return nil on nonnull', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: false, @@ -1356,9 +1379,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1388,9 +1412,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1420,9 +1445,10 @@ void main() { test('named argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1502,13 +1528,13 @@ name: foobar final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1577,12 +1603,12 @@ name: foobar test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1615,9 +1641,10 @@ name: foobar test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1668,13 +1695,13 @@ name: foobar test('host test code handles enums', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, dartHostTestHandler: 'ApiMock', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1721,9 +1748,10 @@ name: foobar test('connection error contains channel name', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'method', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration(baseName: 'Output', isNullable: false), diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index 4570ac633aaa..8264496d3343 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -78,10 +78,10 @@ void main() { }); test('get codec classes from argument type arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -123,10 +123,10 @@ void main() { }); test('get codec classes from return value type arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -168,10 +168,10 @@ void main() { }); test('get codec classes from all arguments', () { - final Api api = - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + final AstFlutterApi api = AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -216,9 +216,10 @@ void main() { test('getCodecClasses: nested type arguments', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -272,12 +273,12 @@ void main() { test('getCodecClasses: with Object', () { final Root root = Root(apis: [ - Api( + AstFlutterApi( name: 'Api1', - location: ApiLocation.flutter, methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -311,12 +312,12 @@ void main() { test('getCodecClasses: unique entries', () { final Root root = Root(apis: [ - Api( + AstFlutterApi( name: 'Api1', - location: ApiLocation.flutter, methods: [ Method( name: 'foo', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -330,12 +331,12 @@ void main() { ) ], ), - Api( + AstHostApi( name: 'Api2', - location: ApiLocation.host, methods: [ Method( name: 'foo', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index cba2d702255c..97cbfe8b4d09 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -123,9 +123,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -265,9 +266,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -318,9 +320,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -359,9 +362,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -403,9 +407,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -440,9 +445,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -578,9 +584,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -639,9 +646,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -745,9 +753,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -875,9 +884,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -910,9 +920,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -945,9 +956,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -977,9 +989,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1010,9 +1023,10 @@ void main() { test('flutter int return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration(baseName: 'int', isNullable: false), parameters: [], @@ -1041,9 +1055,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1082,9 +1097,10 @@ void main() { test('if host argType is Object not cast', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'objectTest', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1110,9 +1126,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1152,9 +1169,10 @@ void main() { test('flutter single args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'send', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1184,9 +1202,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1213,9 +1232,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1244,9 +1264,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1277,9 +1298,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1313,9 +1335,10 @@ void main() { test('background platform channel', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1415,13 +1438,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1496,12 +1519,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1535,9 +1558,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1587,8 +1611,7 @@ void main() { }); test('creates api error class for custom errors', () { - final Api api = - Api(name: 'Api', location: ApiLocation.host, methods: []); + final Api api = AstHostApi(name: 'Api', methods: []); final Root root = Root( apis: [api], classes: [], @@ -1610,12 +1633,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart index f51c68b32bcc..e680e3ac66d2 100644 --- a/packages/pigeon/test/kotlin_generator_test.dart +++ b/packages/pigeon/test/kotlin_generator_test.dart @@ -140,9 +140,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -176,9 +177,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -405,9 +407,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -462,9 +465,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -505,9 +509,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -550,9 +555,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -590,9 +596,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -732,9 +739,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -790,9 +798,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -979,9 +988,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1015,9 +1025,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1051,9 +1062,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1084,9 +1096,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1116,9 +1129,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -1159,9 +1173,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1203,9 +1218,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1232,9 +1248,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1262,9 +1279,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1298,9 +1316,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1333,9 +1352,10 @@ void main() { test('nonnull fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1389,13 +1409,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1470,12 +1490,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1509,9 +1529,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1563,10 +1584,11 @@ void main() { test('creates api error class for custom errors', () { final Method method = Method( name: 'doSomething', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: []); - final Api api = Api( - name: 'SomeApi', location: ApiLocation.host, methods: [method]); + final AstHostApi api = + AstHostApi(name: 'SomeApi', methods: [method]); final Root root = Root( apis: [api], classes: [], @@ -1593,12 +1615,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1639,12 +1661,12 @@ void main() { test('gen host uses default error class', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, methods: [ Method( name: 'method', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1678,12 +1700,12 @@ void main() { test('gen flutter uses default error class', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1717,12 +1739,12 @@ void main() { test('gen host uses error class', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, methods: [ Method( name: 'method', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1759,12 +1781,12 @@ void main() { test('gen flutter uses error class', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index f77f186ac8df..267747e1e913 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -188,9 +188,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -251,9 +252,10 @@ void main() { test('validate nullable primitive enum', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -328,9 +330,10 @@ void main() { test('gen one api header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -383,9 +386,10 @@ void main() { test('gen one api source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -656,9 +660,10 @@ void main() { test('prefix nested class header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -711,9 +716,10 @@ void main() { test('prefix nested class source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -766,9 +772,10 @@ void main() { test('gen flutter api header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -821,9 +828,10 @@ void main() { test('gen flutter api source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -872,9 +880,10 @@ void main() { test('gen host void header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -913,9 +922,10 @@ void main() { test('gen host void source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -956,9 +966,10 @@ void main() { test('gen flutter void return header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -997,9 +1008,10 @@ void main() { test('gen flutter void return source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1039,9 +1051,10 @@ void main() { test('gen host void arg header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1076,9 +1089,10 @@ void main() { test('gen host void arg source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1113,9 +1127,10 @@ void main() { test('gen flutter void arg header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1153,9 +1168,10 @@ void main() { test('gen flutter void arg source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1281,9 +1297,10 @@ void main() { test('gen map argument with object', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1317,9 +1334,10 @@ void main() { test('async void (input) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1367,9 +1385,10 @@ void main() { test('async output(input) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1421,9 +1440,10 @@ void main() { test('async output(void) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1462,9 +1482,10 @@ void main() { test('async void (void) HostApi header', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true) @@ -1493,9 +1514,10 @@ void main() { test('async output(input) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1547,9 +1569,10 @@ void main() { test('async void (input) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1597,9 +1620,10 @@ void main() { test('async void (void) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: const TypeDeclaration.voidDeclaration(), isAsynchronous: true) @@ -1628,9 +1652,10 @@ void main() { test('async output(void) HostApi source', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -1755,9 +1780,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1818,9 +1844,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1878,9 +1905,10 @@ void main() { test('host nested generic argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1926,9 +1954,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -1983,9 +2012,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -2040,9 +2070,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -2110,9 +2141,10 @@ void main() { test('host multiple args async', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -2180,9 +2212,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -2245,25 +2278,48 @@ void main() { Root getDivideRoot(ApiLocation location) => Root( apis: [ - Api(name: 'Api', location: location, methods: [ - Method( - name: 'divide', - objcSelector: 'divideValue:by:', - parameters: [ - Parameter( - type: const TypeDeclaration( - baseName: 'int', isNullable: false), - name: 'x', - ), - Parameter( - type: const TypeDeclaration( - baseName: 'int', isNullable: false), - name: 'y', - ), - ], - returnType: const TypeDeclaration( - baseName: 'double', isNullable: false)) - ]) + switch (location) { + ApiLocation.host => AstHostApi(name: 'Api', methods: [ + Method( + name: 'divide', + location: location, + objcSelector: 'divideValue:by:', + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'x', + ), + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'y', + ), + ], + returnType: const TypeDeclaration( + baseName: 'double', isNullable: false)) + ]), + ApiLocation.flutter => AstFlutterApi(name: 'Api', methods: [ + Method( + name: 'divide', + location: location, + objcSelector: 'divideValue:by:', + parameters: [ + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'x', + ), + Parameter( + type: const TypeDeclaration( + baseName: 'int', isNullable: false), + name: 'y', + ), + ], + returnType: const TypeDeclaration( + baseName: 'double', isNullable: false)) + ]), + } ], classes: [], enums: [], @@ -2378,9 +2434,10 @@ void main() { test('return nullable flutter header', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2414,9 +2471,10 @@ void main() { test('return nullable flutter source', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2447,9 +2505,10 @@ void main() { test('return nullable host header', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2480,9 +2539,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2537,9 +2597,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2593,9 +2654,10 @@ void main() { test('background platform channel', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -2644,13 +2706,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -2725,12 +2787,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -2767,9 +2829,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -2825,12 +2888,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 5d60f92bf69d..7998733664ab 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -313,7 +313,7 @@ abstract class AFlutterApi { expect(results.errors.length, equals(0)); expect(results.root.apis.length, equals(1)); expect(results.root.apis[0].name, equals('AFlutterApi')); - expect(results.root.apis[0].location, equals(ApiLocation.flutter)); + expect(results.root.apis[0], isA()); }); test('void host api', () { @@ -370,8 +370,10 @@ abstract class ApiWithMockDartClass { final ParseResults results = parseSource(code); expect(results.errors.length, equals(0)); expect(results.root.apis.length, equals(1)); - expect(results.root.apis[0].dartHostTestHandler, - equals('ApiWithMockDartClassMock')); + expect( + (results.root.apis[0] as AstHostApi).dartHostTestHandler, + equals('ApiWithMockDartClassMock'), + ); }); test('only visible from nesting', () { diff --git a/packages/pigeon/test/swift_generator_test.dart b/packages/pigeon/test/swift_generator_test.dart index 4c07b784eb74..e43dc8efed62 100644 --- a/packages/pigeon/test/swift_generator_test.dart +++ b/packages/pigeon/test/swift_generator_test.dart @@ -84,9 +84,10 @@ void main() { test('primitive enum host', () { final Root root = Root(apis: [ - Api(name: 'Bar', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Bar', methods: [ Method( name: 'bar', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -120,9 +121,10 @@ void main() { test('gen one host api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -248,9 +250,10 @@ void main() { test('gen one flutter api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -302,9 +305,10 @@ void main() { test('gen host void api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -343,9 +347,10 @@ void main() { test('gen flutter void return api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -385,9 +390,10 @@ void main() { test('gen host void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -423,9 +429,10 @@ void main() { test('gen flutter void argument api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [], returnType: TypeDeclaration( baseName: 'Output', @@ -560,9 +567,10 @@ void main() { test('gen one async Host Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -615,9 +623,10 @@ void main() { test('gen one async Flutter Api', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -798,9 +807,10 @@ void main() { test('host generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -833,9 +843,10 @@ void main() { test('flutter generics argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -868,9 +879,10 @@ void main() { test('host generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -901,9 +913,10 @@ void main() { test('flutter generics return', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration( baseName: 'List', isNullable: false, @@ -936,9 +949,10 @@ void main() { test('host multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.host, parameters: [ Parameter( name: 'x', @@ -979,9 +993,10 @@ void main() { test('flutter multiple args', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'add', + location: ApiLocation.flutter, parameters: [ Parameter( name: 'x', @@ -1023,9 +1038,10 @@ void main() { test('return nullable host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1052,9 +1068,10 @@ void main() { test('return nullable host async', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration( baseName: 'int', isNullable: true, @@ -1085,9 +1102,10 @@ void main() { test('nullable argument host', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.host, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1121,9 +1139,10 @@ void main() { test('nullable argument flutter', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doit', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1156,9 +1175,10 @@ void main() { test('nonnull fields', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.host, parameters: [ Parameter( type: TypeDeclaration( @@ -1210,13 +1230,13 @@ void main() { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'api', - location: ApiLocation.flutter, documentationComments: [comments[count++]], methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), documentationComments: [comments[count++]], parameters: [ @@ -1287,12 +1307,12 @@ void main() { test("doesn't create codecs if no custom datatypes", () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( @@ -1325,9 +1345,10 @@ void main() { test('creates custom codecs if custom datatypes present', () { final Root root = Root(apis: [ - Api(name: 'Api', location: ApiLocation.flutter, methods: [ + AstFlutterApi(name: 'Api', methods: [ Method( name: 'doSomething', + location: ApiLocation.flutter, parameters: [ Parameter( type: TypeDeclaration( @@ -1379,9 +1400,10 @@ void main() { test('swift function signature', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'set', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1422,9 +1444,10 @@ void main() { test('swift function signature with same name argument', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'set', + location: ApiLocation.host, parameters: [ Parameter( type: const TypeDeclaration( @@ -1458,9 +1481,10 @@ void main() { test('swift function signature with no arguments', () { final Root root = Root( apis: [ - Api(name: 'Api', location: ApiLocation.host, methods: [ + AstHostApi(name: 'Api', methods: [ Method( name: 'clear', + location: ApiLocation.host, parameters: [], swiftFunction: 'removeAll()', returnType: const TypeDeclaration.voidDeclaration(), @@ -1486,12 +1510,12 @@ void main() { test('connection error contains channel name', () { final Root root = Root( apis: [ - Api( + AstFlutterApi( name: 'Api', - location: ApiLocation.flutter, methods: [ Method( name: 'method', + location: ApiLocation.flutter, returnType: const TypeDeclaration.voidDeclaration(), parameters: [ Parameter( From abc026ad21bad0a91b532a786aba133f0ea293a6 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:21:38 -0500 Subject: [PATCH 02/15] constructor extend method and make required default to true --- packages/pigeon/lib/ast.dart | 38 +++++++++-------------------- packages/pigeon/lib/pigeon_lib.dart | 1 - 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 7b0e32511fae..db663f18bf38 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -30,7 +30,7 @@ class Method extends Node { required this.returnType, required this.parameters, required this.location, - this.required = false, + this.required = true, this.isAsynchronous = false, this.isStatic = false, this.offset, @@ -184,34 +184,18 @@ class AstProxyApi extends Api { } /// Represents a constructor for an API. -class Constructor extends Node { +class Constructor extends Method { /// Parametric constructor for [Constructor]. Constructor({ - required this.name, - required this.parameters, - this.offset, - this.swiftFunction = '', - this.documentationComments = const [], - }); - - /// The name of the method. - String name; - - /// The parameters passed into the [Constructor]. - List parameters; - - /// The offset in the source file where the field appears. - int? offset; - - /// An override for the generated swift function signature (ex. "divideNumber(_:by:)"). - String swiftFunction; - - /// List of documentation comments, separated by line. - /// - /// Lines should not include the comment marker itself, but should include any - /// leading whitespace, so that any indentation in the original comment is preserved. - /// For example: [" List of documentation comments, separated by line.", ...] - List documentationComments; + required super.name, + required super.parameters, + super.offset, + super.swiftFunction = '', + super.documentationComments = const [], + }) : super( + returnType: const TypeDeclaration.voidDeclaration(), + location: ApiLocation.host, + ); @override String toString() { diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 2b3bda5f7ca1..ca268c809eab 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -1667,7 +1667,6 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { typeAnnotationsToTypeArguments(returnType.typeArguments), isNullable: returnType.question != null), parameters: arguments, - required: true, isStatic: isStatic, location: switch (_currentApi!) { AstHostApi() => ApiLocation.host, From 0c69e1dc171a03343b5ced995be5faa305dbcfd3 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:50:08 -0500 Subject: [PATCH 03/15] use helper method --- packages/pigeon/lib/pigeon_lib.dart | 167 ++++++++++++++-------------- 1 file changed, 86 insertions(+), 81 deletions(-) diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index ca268c809eab..9791622b8a3c 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -1766,87 +1766,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { lineNumber: _calculateLineNumber(source, node.offset))); } } else if (_currentApi is AstProxyApi) { - final bool isStatic = _hasMetadata(node.metadata, 'static'); - if (type is dart_ast.GenericFunctionType) { - final List parameters = type.parameters.parameters - .map(formalParameterToPigeonParameter) - .toList(); - final String swiftFunction = - _findMetadata(node.metadata, 'SwiftFunction') - ?.arguments - ?.arguments - .first - .asNullable() - ?.value ?? - ''; - final dart_ast.ArgumentList? taskQueueArguments = - _findMetadata(node.metadata, 'TaskQueue')?.arguments; - final String? taskQueueTypeName = taskQueueArguments == null - ? null - : getFirstChildOfType(taskQueueArguments) - ?.expression - .asNullable() - ?.name; - final TaskQueueType taskQueueType = - _stringToEnum(TaskQueueType.values, taskQueueTypeName) ?? - TaskQueueType.serial; - - // Methods without named return types aren't supported. - final dart_ast.TypeAnnotation returnType = type.returnType!; - returnType as dart_ast.NamedType; - - _currentApi!.methods.add( - Method( - name: node.fields.variables[0].name.lexeme, - returnType: TypeDeclaration( - baseName: _getNamedTypeQualifiedName(returnType), - typeArguments: - typeAnnotationsToTypeArguments(returnType.typeArguments), - isNullable: returnType.question != null, - ), - location: ApiLocation.flutter, - required: type.question == null, - isStatic: isStatic, - parameters: parameters, - isAsynchronous: _hasMetadata(node.metadata, 'async'), - swiftFunction: swiftFunction, - offset: node.offset, - taskQueueType: taskQueueType, - documentationComments: - _documentationCommentsParser(node.documentationComment?.tokens), - ), - ); - } else if (type is dart_ast.NamedType) { - final _FindInitializer findInitializerVisitor = _FindInitializer(); - node.visitChildren(findInitializerVisitor); - if (findInitializerVisitor.initializer != null) { - _errors.add(Error( - message: - 'Initialization isn\'t supported for fields in ProxyApis ("$node"), just use nullable types with no initializer (example "int? x;").', - lineNumber: _calculateLineNumber(source, node.offset))); - } else { - final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; - (_currentApi as AstProxyApi?)!.fields.add( - Field( - type: TypeDeclaration( - baseName: _getNamedTypeQualifiedName(type), - isNullable: type.question != null, - typeArguments: typeAnnotationsToTypeArguments( - typeArguments, - ), - ), - name: node.fields.variables[0].name.lexeme, - isAttached: - _hasMetadata(node.metadata, 'attached') || isStatic, - isStatic: isStatic, - offset: node.offset, - documentationComments: _documentationCommentsParser( - node.documentationComment?.tokens, - ), - ), - ); - } - } + _addProxyApiField(type, node); } else if (_currentApi != null) { _errors.add(Error( message: 'Fields aren\'t supported in Pigeon API classes ("$node").', @@ -1919,6 +1839,91 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } return node.name2.lexeme; } + + void _addProxyApiField( + dart_ast.TypeAnnotation? type, + dart_ast.FieldDeclaration node, + ) { + final bool isStatic = _hasMetadata(node.metadata, 'static'); + if (type is dart_ast.GenericFunctionType) { + final List parameters = type.parameters.parameters + .map(formalParameterToPigeonParameter) + .toList(); + final String swiftFunction = _findMetadata(node.metadata, 'SwiftFunction') + ?.arguments + ?.arguments + .first + .asNullable() + ?.value ?? + ''; + final dart_ast.ArgumentList? taskQueueArguments = + _findMetadata(node.metadata, 'TaskQueue')?.arguments; + final String? taskQueueTypeName = taskQueueArguments == null + ? null + : getFirstChildOfType(taskQueueArguments) + ?.expression + .asNullable() + ?.name; + final TaskQueueType taskQueueType = + _stringToEnum(TaskQueueType.values, taskQueueTypeName) ?? + TaskQueueType.serial; + + // Methods without named return types aren't supported. + final dart_ast.TypeAnnotation returnType = type.returnType!; + returnType as dart_ast.NamedType; + + _currentApi!.methods.add( + Method( + name: node.fields.variables[0].name.lexeme, + returnType: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(returnType), + typeArguments: + typeAnnotationsToTypeArguments(returnType.typeArguments), + isNullable: returnType.question != null, + ), + location: ApiLocation.flutter, + required: type.question == null, + isStatic: isStatic, + parameters: parameters, + isAsynchronous: _hasMetadata(node.metadata, 'async'), + swiftFunction: swiftFunction, + offset: node.offset, + taskQueueType: taskQueueType, + documentationComments: + _documentationCommentsParser(node.documentationComment?.tokens), + ), + ); + } else if (type is dart_ast.NamedType) { + final _FindInitializer findInitializerVisitor = _FindInitializer(); + node.visitChildren(findInitializerVisitor); + if (findInitializerVisitor.initializer != null) { + _errors.add(Error( + message: + 'Initialization isn\'t supported for fields in ProxyApis ("$node"), just use nullable types with no initializer (example "int? x;").', + lineNumber: _calculateLineNumber(source, node.offset))); + } else { + final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; + (_currentApi as AstProxyApi?)!.fields.add( + Field( + type: TypeDeclaration( + baseName: _getNamedTypeQualifiedName(type), + isNullable: type.question != null, + typeArguments: typeAnnotationsToTypeArguments( + typeArguments, + ), + ), + name: node.fields.variables[0].name.lexeme, + isAttached: _hasMetadata(node.metadata, 'attached') || isStatic, + isStatic: isStatic, + offset: node.offset, + documentationComments: _documentationCommentsParser( + node.documentationComment?.tokens, + ), + ), + ); + } + } + } } int? _calculateLineNumberNullable(String contents, int? offset) { From eb14522bf8252e1e82b2cb95b94bb116ef70b31a Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:20:20 -0500 Subject: [PATCH 04/15] fix some validation and add tests --- packages/pigeon/lib/generator_tools.dart | 13 ++ packages/pigeon/lib/pigeon_lib.dart | 31 ++-- packages/pigeon/test/pigeon_lib_test.dart | 212 ++++++++++++++++++++++ 3 files changed, 240 insertions(+), 16 deletions(-) diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 4fba3835620e..7fa2f967cf9a 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -437,6 +437,19 @@ Map> getReferencedTypes( } references.addMany(_getTypeArguments(method.returnType), method.offset); } + if (api is AstProxyApi) { + for (final Constructor constructor in api.constructors) { + for (final NamedType parameter in constructor.parameters) { + references.addMany( + _getTypeArguments(parameter.type), + parameter.offset, + ); + } + } + for (final Field field in api.fields) { + references.addMany(_getTypeArguments(field.type), field.offset); + } + } } final Set referencedTypeNames = diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 9791622b8a3c..1e69a4818415 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -974,34 +974,34 @@ List _validateProxyApi( result.add(Error(message: error.toString())); } - // Validate that the api does not inherit a non attached field from its super class. - if (superClassChain != null && - superClassChain.isNotEmpty && - superClassChain.first.unattachedFields.isNotEmpty) { + // Validate that the api does not inherit an unattached field from its super class. + final AstProxyApi? directSuperClass = superClassChain?.firstOrNull; + if (directSuperClass != null && + directSuperClass.unattachedFields.isNotEmpty) { result.add(Error( message: - 'Non attached fields can not be inherited. Non attached field found for parent class ${api.unattachedFields.first.name}', + 'Unattached fields can not be inherited. Unattached field found for parent class: ${directSuperClass.unattachedFields.first.name}', lineNumber: _calculateLineNumberNullable( source, - api.unattachedFields.first.offset, + directSuperClass.unattachedFields.first.offset, ), )); } + final bool hasUnattachedField = api.unattachedFields.isNotEmpty; + final bool hasRequiredFlutterMethod = + api.flutterMethods.any((Method method) => method.required); for (final AstProxyApi proxyApi in proxyApis) { // Validate this api is not used as an attached field while either: // 1. Having an unattached field. // 2. Having a required Flutter method. - final bool hasUnattachedField = api.unattachedFields.isNotEmpty; - final bool hasRequiredFlutterMethod = - api.flutterMethods.any((Method method) => method.required); if (hasUnattachedField || hasRequiredFlutterMethod) { for (final Field field in proxyApi.attachedFields) { if (field.type.baseName == api.name) { if (hasUnattachedField) { result.add(Error( message: - 'ProxyApis with fields can not be used as attached fields: ${field.name}', + 'ProxyApis with unattached fields can not be used as attached fields: ${field.name}', lineNumber: _calculateLineNumberNullable( source, field.offset, @@ -1032,7 +1032,7 @@ List _validateProxyApi( if (interfaceName == api.name) { result.add(Error( message: - 'ProxyApis used as interfaces can only have callback methods: ${proxyApi.name}', + 'ProxyApis used as interfaces can only have callback methods: `${proxyApi.name}` implements `${api.name}`', )); } } @@ -1139,28 +1139,27 @@ List _validateProxyApi( if (!isProxyApi(field)) { result.add(Error( message: - 'Static fields are considered attached fields and must be a ProxyApi: ${field.type.baseName}.', + 'Static fields are considered attached fields and must be a ProxyApi: ${field.type.baseName}', lineNumber: _calculateLineNumberNullable(source, field.offset), )); } else if (field.type.isNullable) { result.add(Error( message: - 'Static fields are considered attached fields and must not be nullable: ${field.type.baseName}.', + 'Static fields are considered attached fields and must not be nullable: ${field.type.baseName}?', lineNumber: _calculateLineNumberNullable(source, field.offset), )); } } else if (field.isAttached) { if (!isProxyApi(field)) { result.add(Error( - message: - 'Attached fields must be a ProxyApi: ${field.type.baseName}.', + message: 'Attached fields must be a ProxyApi: ${field.type.baseName}', lineNumber: _calculateLineNumberNullable(source, field.offset), )); } if (field.type.isNullable) { result.add(Error( message: - 'Attached fields must not be nullable: ${field.type.baseName}.', + 'Attached fields must not be nullable: ${field.type.baseName}?', lineNumber: _calculateLineNumberNullable(source, field.offset), )); } diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 7998733664ab..54d9034ebdb7 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:pigeon/ast.dart'; import 'package:pigeon/generator_tools.dart'; import 'package:pigeon/pigeon_lib.dart'; +import 'package:test/expect.dart'; import 'package:test/test.dart'; class _ValidatorGeneratorAdapter implements GeneratorAdapter { @@ -1354,4 +1355,215 @@ abstract class Api { expect(results.errors[0].message, contains('FlutterApi method parameters must not be optional')); }); + + test('simple parse ProxyApi', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + MyClass(); + late String aField; + late void Function() aCallbackMethod; + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors.length, equals(0)); + final Root root = parseResult.root; + expect(root.apis.length, equals(1)); + + final AstProxyApi proxyApi = root.apis.single as AstProxyApi; + expect(proxyApi.name, equals('MyClass')); + expect(proxyApi.constructors.single.name, equals('')); + expect(proxyApi.methods.length, equals(2)); + + for (final Method method in proxyApi.methods) { + if (method.location == ApiLocation.host) { + expect(method.name, equals('aMethod')); + } else if (method.location == ApiLocation.flutter) { + expect(method.name, equals('aCallbackMethod')); + } + } + }); + + group('ProxyApi validation', () { + test('error with using data class', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass { + MyClass(DataClass input); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors.length, equals(1)); + expect( + parseResult.errors.single.message, + contains('ProxyApis do not support data classes'), + ); + }); + + test('super class must be proxy api', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass extends DataClass { + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Super class of MyClass is not marked as a @ProxyApi'), + ); + }); + + test('interface must be proxy api', () { + const String code = ''' +class DataClass { + late int input; +} + +@ProxyApi() +abstract class MyClass implements DataClass { + void aMethod(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Interface of MyClass is not marked as a @ProxyApi'), + ); + }); + + test('unattached fields can not be inherited', () { + const String code = ''' +@ProxyApi() +abstract class MyClass extends MyOtherClass { } + +@ProxyApi() +abstract class MyOtherClass { + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'Unattached fields can not be inherited. Unattached field found for parent class: aField', + ), + ); + }); + + test( + 'api is not used as an attached field while having an unattached field', + () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyOtherClass anAttachedField; +} + +@ProxyApi() +abstract class MyOtherClass { + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis with unattached fields can not be used as attached fields: anAttachedField', + ), + ); + }); + + test( + 'api is not used as an attached field while having a required Flutter method', + () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyOtherClass anAttachedField; +} + +@ProxyApi() +abstract class MyOtherClass { + late void Function() aCallbackMethod; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis with required callback methods can not be used as attached fields: anAttachedField', + ), + ); + }); + + test('interfaces can only have callback methods', () { + const String code = ''' +@ProxyApi() +abstract class MyClass implements MyOtherClass { +} + +@ProxyApi() +abstract class MyOtherClass { + MyOtherClass(); +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains( + 'ProxyApis used as interfaces can only have callback methods: `MyClass` implements `MyOtherClass`', + ), + ); + }); + + test('attached fields must be a ProxyApi', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late int aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Attached fields must be a ProxyApi: int'), + ); + }); + + test('attached fields must not be nullable', () { + const String code = ''' +@ProxyApi() +abstract class MyClass { + @attached + late MyClass? aField; +} +'''; + final ParseResults parseResult = parseSource(code); + expect(parseResult.errors, isNotEmpty); + expect( + parseResult.errors[0].message, + contains('Attached fields must not be nullable: MyClass?'), + ); + }); + }); } From 155469f6e6ee6bae60aa1ef6718836e1ae323115 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:23:59 -0500 Subject: [PATCH 05/15] change required to isRequired --- packages/pigeon/lib/ast.dart | 4 ++-- packages/pigeon/lib/pigeon_lib.dart | 4 ++-- packages/pigeon/test/cpp_generator_test.dart | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index db663f18bf38..cb1166a04c0d 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -30,7 +30,7 @@ class Method extends Node { required this.returnType, required this.parameters, required this.location, - this.required = true, + this.isRequired = true, this.isAsynchronous = false, this.isStatic = false, this.offset, @@ -78,7 +78,7 @@ class Method extends Node { /// /// This flag is typically used to determine whether a callback method for /// a `ProxyApi` is nullable or not. - bool required; + bool isRequired; /// Whether this is a static method of a ProxyApi. bool isStatic; diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 1e69a4818415..e58f0cb94bbf 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -990,7 +990,7 @@ List _validateProxyApi( final bool hasUnattachedField = api.unattachedFields.isNotEmpty; final bool hasRequiredFlutterMethod = - api.flutterMethods.any((Method method) => method.required); + api.flutterMethods.any((Method method) => method.isRequired); for (final AstProxyApi proxyApi in proxyApis) { // Validate this api is not used as an attached field while either: // 1. Having an unattached field. @@ -1881,7 +1881,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { isNullable: returnType.question != null, ), location: ApiLocation.flutter, - required: type.question == null, + isRequired: type.question == null, isStatic: isStatic, parameters: parameters, isAsynchronous: _hasMetadata(node.metadata, 'async'), diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index af7865815d3a..861ba5082b46 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -37,7 +37,7 @@ void main() { ), name: 'input') ], - required: true, + isRequired: true, location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', From 3a45694ab9b1458c10357946e033623a1a77e4d9 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:37:51 -0500 Subject: [PATCH 06/15] improve docs --- packages/pigeon/lib/cpp_generator.dart | 3 +-- packages/pigeon/lib/pigeon_lib.dart | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/pigeon/lib/cpp_generator.dart b/packages/pigeon/lib/cpp_generator.dart index 2ec8ccd00833..419695b4ed2b 100644 --- a/packages/pigeon/lib/cpp_generator.dart +++ b/packages/pigeon/lib/cpp_generator.dart @@ -175,7 +175,7 @@ class CppHeaderGenerator extends StructuredGenerator { indent, anEnum.documentationComments, _docCommentSpec); indent.write('enum class ${anEnum.name} '); indent.addScoped('{', '};', () { - enumerate(anEnum.members, (int index, EnumMember member) { + enumerate(anEnum.members, (int index, final EnumMember member) { addDocumentationComments( indent, member.documentationComments, _docCommentSpec); indent.writeln( @@ -784,7 +784,6 @@ class CppSourceGenerator extends StructuredGenerator { final Iterable<_IndexedField> indexedFields = indexMap( getFieldsInSerializationOrder(classDefinition), (int index, NamedType field) => _IndexedField(index, field)); - final Iterable<_IndexedField> nullableFields = indexedFields .where((_IndexedField field) => field.field.type.isNullable); final Iterable<_IndexedField> nonNullableFields = indexedFields diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index e58f0cb94bbf..5de9cdb5492f 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -52,14 +52,14 @@ const Object async = _Asynchronous(); /// Metadata to annotate the field of a ProxyApi as an Attached Field. /// -/// Attached fields provide a synchronous [AstProxyApi] instance as a field -/// for another [AstProxyApi]. +/// Attached fields provide a synchronous [ProxyApi] instance as a field for +/// another [ProxyApi]. /// /// Attached fields: /// * Must be nonnull. -/// * Must be a ProxyApi type. -/// * Must be a ProxyApi that contains any fields. -/// * Must be a ProxyApi that does not have a required Flutter method. +/// * Must be a ProxyApi (a class annotated with `@ProxyApi()`). +/// * Must not contain any unattached fields. +/// * Must not have a required callback Flutter method. /// /// Example generated code: /// @@ -74,7 +74,10 @@ const Object async = _Asynchronous(); /// to how constructors are implemented. const Object attached = _Attached(); -/// Metadata to annotate an method or an Attached Field of a ProxyApi as static. +/// Metadata to annotate a field of a ProxyApi as static. +/// +/// Static fields are the same as [attached] fields except the field is static +/// and not attached to any instance of the ProxyApi. const Object static = _Static(); /// Metadata annotation used to configure how Pigeon will generate code. @@ -129,10 +132,10 @@ class FlutterApi { /// constructors, fields, methods and host↔Dart methods used to wrap a native /// class. /// -/// The generated Dart class acts as a proxy to a native type and -/// maintains instances automatically with an `InstanceManager`. The generated -/// host language class implements methods to interact with class instances -/// or static methods. +/// The generated Dart class acts as a proxy to a native type and maintains +/// instances automatically with an `InstanceManager`. The generated host +/// language class implements methods to interact with class instances or static +/// methods. class ProxyApi { /// Parametric constructor for [ProxyApi]. const ProxyApi({this.superClass}); From 635035349ae25edc321b74a11e247caaa2ffc6db Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:46:21 -0500 Subject: [PATCH 07/15] fix lint errors and hide ProxyApi --- packages/pigeon/lib/pigeon.dart | 2 +- packages/pigeon/test/cpp_generator_test.dart | 1 - packages/pigeon/test/pigeon_lib_test.dart | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart index 7f2fb7505fa8..e92619076c0b 100644 --- a/packages/pigeon/lib/pigeon.dart +++ b/packages/pigeon/lib/pigeon.dart @@ -9,5 +9,5 @@ export 'dart_generator.dart' show DartOptions; export 'java_generator.dart' show JavaOptions; export 'kotlin_generator.dart' show KotlinOptions; export 'objc_generator.dart' show ObjcOptions; -export 'pigeon_lib.dart'; +export 'pigeon_lib.dart' hide ProxyApi; export 'swift_generator.dart' show SwiftOptions; diff --git a/packages/pigeon/test/cpp_generator_test.dart b/packages/pigeon/test/cpp_generator_test.dart index 861ba5082b46..42aa28c5361a 100644 --- a/packages/pigeon/test/cpp_generator_test.dart +++ b/packages/pigeon/test/cpp_generator_test.dart @@ -37,7 +37,6 @@ void main() { ), name: 'input') ], - isRequired: true, location: ApiLocation.host, returnType: TypeDeclaration( baseName: 'Output', diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 54d9034ebdb7..34516e8c0dea 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -8,7 +8,6 @@ import 'dart:io'; import 'package:pigeon/ast.dart'; import 'package:pigeon/generator_tools.dart'; import 'package:pigeon/pigeon_lib.dart'; -import 'package:test/expect.dart'; import 'package:test/test.dart'; class _ValidatorGeneratorAdapter implements GeneratorAdapter { From 4ce1e2146c73b257b7a4bfae0c230cfbfe6167da Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:49:33 -0500 Subject: [PATCH 08/15] add hide comment --- packages/pigeon/lib/pigeon.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart index e92619076c0b..a2c6026e3117 100644 --- a/packages/pigeon/lib/pigeon.dart +++ b/packages/pigeon/lib/pigeon.dart @@ -9,5 +9,7 @@ export 'dart_generator.dart' show DartOptions; export 'java_generator.dart' show JavaOptions; export 'kotlin_generator.dart' show KotlinOptions; export 'objc_generator.dart' show ObjcOptions; +// TODO(bparrishMines): Remove hide once implementation of the api is finished +// for Dart and one host language. export 'pigeon_lib.dart' hide ProxyApi; export 'swift_generator.dart' show SwiftOptions; From 22c30651d56303d3cddbb6270567051c8623b10f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:53:51 -0500 Subject: [PATCH 09/15] test for recursive looks --- .../pigeon/test/generator_tools_test.dart | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index 8264496d3343..c747eeda30b6 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -373,4 +373,97 @@ void main() { expect(dartPackageName, 'pigeon'); }); + + test('recursiveGetSuperClassApisChain', () { + final AstProxyApi api = AstProxyApi( + name: 'Api', + methods: [], + constructors: [], + fields: [], + superClassName: 'Api2', + ); + final AstProxyApi superClassApi = AstProxyApi( + name: 'Api2', + methods: [], + constructors: [], + fields: [], + superClassName: 'Api3', + ); + final AstProxyApi superClassOfSuperClassApi = AstProxyApi( + name: 'Api3', + methods: [], + constructors: [], + fields: [], + ); + + final List apiChain = recursiveGetSuperClassApisChain( + api, + [superClassOfSuperClassApi, api, superClassApi], + ); + + expect( + apiChain, + containsAllInOrder([ + superClassApi, + superClassOfSuperClassApi, + ]), + ); + }); + + test('recursiveFindAllInterfacesApis', () { + final AstProxyApi api = AstProxyApi( + name: 'Api', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'Api2', 'Api3'}, + ); + final AstProxyApi interfaceApi = AstProxyApi( + name: 'Api2', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'Api4', 'Api5'}, + ); + final AstProxyApi interfaceApi2 = AstProxyApi( + name: 'Api3', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'Api5'}, + ); + final AstProxyApi interfaceOfInterfaceApi = AstProxyApi( + name: 'Api4', + methods: [], + constructors: [], + fields: [], + ); + final AstProxyApi interfaceOfInterfaceApi2 = AstProxyApi( + name: 'Api5', + methods: [], + constructors: [], + fields: [], + ); + + final Set allInterfaces = recursiveFindAllInterfacesApis( + api, + [ + api, + interfaceApi, + interfaceApi2, + interfaceOfInterfaceApi, + interfaceOfInterfaceApi2, + ], + ); + + expect( + allInterfaces, + containsAll([ + interfaceApi, + interfaceApi2, + interfaceOfInterfaceApi, + interfaceOfInterfaceApi2, + ]), + ); + }); } From a14485edce2ce114de73cc79ebffb2e7e5e47b9d Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:06:15 -0500 Subject: [PATCH 10/15] fix tools version --- packages/pigeon/lib/generator_tools.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 7fa2f967cf9a..43d26c388b44 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -13,7 +13,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '16.0.0'; +const String pigeonVersion = '16.0.1'; /// Read all the content from [stdin] to a String. String readStdin() { From 360dd7a2073dd5aa598728cf6985c3d37de2a2e9 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:23:05 -0500 Subject: [PATCH 11/15] review comments --- packages/pigeon/lib/ast.dart | 36 +++++++++++-------- packages/pigeon/lib/generator_tools.dart | 2 +- packages/pigeon/lib/pigeon_lib.dart | 26 +++++++------- .../pigeon/test/generator_tools_test.dart | 16 ++++----- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index cb1166a04c0d..51bcd215cccf 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -145,7 +145,7 @@ class AstProxyApi extends Api { final List constructors; /// List of fields inside the API. - List fields; + List fields; /// Name of the class this class considers the super class. final String? superClassName; @@ -166,20 +166,22 @@ class AstProxyApi extends Api { /// All fields that are attached. /// /// See [attached]. - Iterable get attachedFields => fields.where( - (Field field) => field.isAttached, + Iterable get attachedFields => fields.where( + (ApiField field) => field.isAttached, ); /// All fields that are not attached. /// /// See [attached]. - Iterable get unattachedFields => fields.where( - (Field field) => !field.isAttached, + Iterable get unattachedFields => fields.where( + (ApiField field) => !field.isAttached, ); @override String toString() { - return '(ProxyApi name:$name methods:$methods documentationComments:$documentationComments superClassName:$superClassName interfacesNames:$interfacesNames)'; + return '(ProxyApi name:$name methods:$methods field:$fields ' + 'documentationComments:$documentationComments ' + 'superClassName:$superClassName interfacesNames:$interfacesNames)'; } } @@ -206,9 +208,9 @@ class Constructor extends Method { } /// Represents a field of an API. -class Field extends NamedType { - /// Constructor for [Field]. - Field({ +class ApiField extends NamedType { + /// Constructor for [ApiField]. + ApiField({ required super.name, required super.type, super.offset, @@ -229,8 +231,8 @@ class Field extends NamedType { /// Returns a copy of [Parameter] instance with new attached [TypeDeclaration]. @override - Field copyWithType(TypeDeclaration type) { - return Field( + ApiField copyWithType(TypeDeclaration type) { + return ApiField( name: name, type: type, offset: offset, @@ -239,6 +241,12 @@ class Field extends NamedType { isStatic: isStatic, ); } + + @override + String toString() { + return '(Field name:$name type:$type isAttached:$isAttached ' + 'isStatic:$isStatic documentationComments:$documentationComments)'; + } } /// Represents a collection of [Method]s. @@ -315,12 +323,12 @@ class TypeDeclaration { /// Associated [Class], if any. final Class? associatedClass; - /// Associated [AstProxyApi], if any. - final AstProxyApi? associatedProxyApi; - /// Whether the [TypeDeclaration] has an [associatedProxyApi]. bool get isProxyApi => associatedProxyApi != null; + /// Associated [AstProxyApi], if any. + final AstProxyApi? associatedProxyApi; + @override int get hashCode { // This has to be implemented because TypeDeclaration is used as a Key to a diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 43d26c388b44..65f07b342450 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -446,7 +446,7 @@ Map> getReferencedTypes( ); } } - for (final Field field in api.fields) { + for (final ApiField field in api.fields) { references.addMany(_getTypeArguments(field.type), field.offset); } } diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 5de9cdb5492f..7b60e526b2f4 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -999,7 +999,7 @@ List _validateProxyApi( // 1. Having an unattached field. // 2. Having a required Flutter method. if (hasUnattachedField || hasRequiredFlutterMethod) { - for (final Field field in proxyApi.attachedFields) { + for (final ApiField field in proxyApi.attachedFields) { if (field.type.baseName == api.name) { if (hasUnattachedField) { result.add(Error( @@ -1049,7 +1049,7 @@ List _validateProxyApi( result.add(unsupportedDataClassError(parameter)); } - if (api.fields.any((Field field) => field.name == parameter.name) || + if (api.fields.any((ApiField field) => field.name == parameter.name) || api.flutterMethods .any((Method method) => method.name == parameter.name)) { result.add(Error( @@ -1135,7 +1135,7 @@ List _validateProxyApi( } // Validate fields - for (final Field field in api.fields) { + for (final ApiField field in api.fields) { if (isDataClass(field)) { result.add(unsupportedDataClassError(field)); } else if (field.isStatic) { @@ -1287,7 +1287,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List fields = []; for (final NamedType field in classDefinition.fields) { fields.add(field.copyWithType( - _attachClassesEnumsAndProxyApis(field.type), + _attachAssociatedDefinition(field.type), )); } classDefinition.fields = fields; @@ -1298,27 +1298,27 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { final List paramList = []; for (final Parameter param in func.parameters) { paramList.add(param.copyWithType( - _attachClassesEnumsAndProxyApis(param.type), + _attachAssociatedDefinition(param.type), )); } func.parameters = paramList; - func.returnType = _attachClassesEnumsAndProxyApis(func.returnType); + func.returnType = _attachAssociatedDefinition(func.returnType); } if (api is AstProxyApi) { for (final Constructor constructor in api.constructors) { final List paramList = []; for (final Parameter param in constructor.parameters) { paramList.add( - param.copyWithType(_attachClassesEnumsAndProxyApis(param.type)), + param.copyWithType(_attachAssociatedDefinition(param.type)), ); } constructor.parameters = paramList; } - final List fieldList = []; - for (final Field field in api.fields) { + final List fieldList = []; + for (final ApiField field in api.fields) { fieldList.add(field.copyWithType( - _attachClassesEnumsAndProxyApis(field.type), + _attachAssociatedDefinition(field.type), )); } api.fields = fieldList; @@ -1334,7 +1334,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { ); } - TypeDeclaration _attachClassesEnumsAndProxyApis(TypeDeclaration type) { + TypeDeclaration _attachAssociatedDefinition(TypeDeclaration type) { final Enum? assocEnum = _enums.firstWhereOrNull( (Enum enumDefinition) => enumDefinition.name == type.baseName); final Class? assocClass = _classes.firstWhereOrNull( @@ -1512,7 +1512,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { name: node.name.lexeme, methods: [], constructors: [], - fields: [], + fields: [], superClassName: superClassName ?? node.extendsClause?.superclass.name2.lexeme, interfacesNames: node.implementsClause?.interfaces @@ -1906,7 +1906,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } else { final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; (_currentApi as AstProxyApi?)!.fields.add( - Field( + ApiField( type: TypeDeclaration( baseName: _getNamedTypeQualifiedName(type), isNullable: type.question != null, diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index c747eeda30b6..bec70e52c5ad 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -379,21 +379,21 @@ void main() { name: 'Api', methods: [], constructors: [], - fields: [], + fields: [], superClassName: 'Api2', ); final AstProxyApi superClassApi = AstProxyApi( name: 'Api2', methods: [], constructors: [], - fields: [], + fields: [], superClassName: 'Api3', ); final AstProxyApi superClassOfSuperClassApi = AstProxyApi( name: 'Api3', methods: [], constructors: [], - fields: [], + fields: [], ); final List apiChain = recursiveGetSuperClassApisChain( @@ -415,34 +415,34 @@ void main() { name: 'Api', methods: [], constructors: [], - fields: [], + fields: [], interfacesNames: {'Api2', 'Api3'}, ); final AstProxyApi interfaceApi = AstProxyApi( name: 'Api2', methods: [], constructors: [], - fields: [], + fields: [], interfacesNames: {'Api4', 'Api5'}, ); final AstProxyApi interfaceApi2 = AstProxyApi( name: 'Api3', methods: [], constructors: [], - fields: [], + fields: [], interfacesNames: {'Api5'}, ); final AstProxyApi interfaceOfInterfaceApi = AstProxyApi( name: 'Api4', methods: [], constructors: [], - fields: [], + fields: [], ); final AstProxyApi interfaceOfInterfaceApi2 = AstProxyApi( name: 'Api5', methods: [], constructors: [], - fields: [], + fields: [], ); final Set allInterfaces = recursiveFindAllInterfacesApis( From ea9f7c3c1f8771382403ebc4d205e453f9406918 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:41:28 -0500 Subject: [PATCH 12/15] switch to a method that looks for matching prefix --- packages/pigeon/lib/pigeon_lib.dart | 126 ++++++++++++++-------------- 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index 7b60e526b2f4..fc890be6d27f 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -867,18 +867,18 @@ List _validateAst(Root root, String source) { 'Parameters must specify their type in method "${method.name}" in API: "${api.name}"', lineNumber: _calculateLineNumberNullable(source, param.offset), )); - } else if (param.name.startsWith('__pigeon_')) { - result.add(Error( - message: - 'Parameter name must not begin with "__pigeon_" in method "${method.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, param.offset), - )); - } else if (param.name == 'pigeonChannelCodec') { - result.add(Error( - message: - 'Parameter name must not be "pigeonChannelCodec" in method "${method.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, param.offset), - )); + } else { + final String? matchingPrefix = _findMatchingPrefixOrNull( + method.name, + prefixes: ['__pigeon_', 'pigeonChannelCodec'], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Parameter name must not begin with "$matchingPrefix" in method "${method.name} in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, param.offset), + )); + } } if (api is AstFlutterApi) { if (!param.isPositional) { @@ -1065,30 +1065,23 @@ List _validateProxyApi( 'Parameters must specify their type in constructor "${constructor.name}" in API: "${api.name}"', lineNumber: _calculateLineNumberNullable(source, parameter.offset), )); - } else if (parameter.name.startsWith('__pigeon_')) { - result.add(Error( - message: - 'Parameter name must not begin with "__pigeon_" in constructor "${constructor.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, parameter.offset), - )); - } else if (parameter.name == 'pigeonChannelCodec') { - result.add(Error( - message: - 'Parameter name must not be "pigeonChannelCodec" in constructor "${constructor.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, parameter.offset), - )); - } else if (parameter.name.startsWith(classNamePrefix)) { - result.add(Error( - message: - 'Parameter name must not begin with "$classNamePrefix" in constructor "${constructor.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, parameter.offset), - )); - } else if (parameter.name.startsWith(classMemberNamePrefix)) { - result.add(Error( - message: - 'Parameter name must not begin with "$classMemberNamePrefix" in constructor "${constructor.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, parameter.offset), - )); + } else { + final String? matchingPrefix = _findMatchingPrefixOrNull( + parameter.name, + prefixes: [ + '__pigeon_', + 'pigeonChannelCodec', + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { + result.add(Error( + message: + 'Parameter name must not begin with "$matchingPrefix" in constructor "${constructor.name} in API: "${api.name}"', + lineNumber: _calculateLineNumberNullable(source, parameter.offset), + )); + } } } if (constructor.swiftFunction.isNotEmpty) { @@ -1111,16 +1104,17 @@ List _validateProxyApi( result.add(unsupportedDataClassError(parameter)); } - if (parameter.name.startsWith(classNamePrefix)) { - result.add(Error( - message: - 'Parameter name must not begin with "$classNamePrefix" in method "${method.name}" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, parameter.offset), - )); - } else if (parameter.name.startsWith(classMemberNamePrefix)) { + final String? matchingPrefix = _findMatchingPrefixOrNull( + parameter.name, + prefixes: [ + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { result.add(Error( message: - 'Parameter name must not begin with "$classMemberNamePrefix" in method "${method.name}" in API: "${api.name}"', + 'Parameter name must not begin with "$matchingPrefix" in method "${method.name} in API: "${api.name}"', lineNumber: _calculateLineNumberNullable(source, parameter.offset), )); } @@ -1168,28 +1162,19 @@ List _validateProxyApi( } } - if (field.name.startsWith('__pigeon_')) { - result.add(Error( - message: - 'Field name must not begin with "__pigeon_" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, field.offset), - )); - } else if (field.name == 'pigeonChannelCodec') { - result.add(Error( - message: - 'Field name must not be "pigeonChannelCodec" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, field.offset), - )); - } else if (field.name.startsWith(classNamePrefix)) { - result.add(Error( - message: - 'Field name must not begin with "$classNamePrefix" in API: "${api.name}"', - lineNumber: _calculateLineNumberNullable(source, field.offset), - )); - } else if (field.name.startsWith(classMemberNamePrefix)) { + final String? matchingPrefix = _findMatchingPrefixOrNull( + field.name, + prefixes: [ + '__pigeon_', + 'pigeonChannelCodec', + classNamePrefix, + classMemberNamePrefix, + ], + ); + if (matchingPrefix != null) { result.add(Error( message: - 'Field name must not begin with "$classMemberNamePrefix" in API: "${api.name}"', + 'Field name must not begin with "$matchingPrefix" in API: "${api.name}"', lineNumber: _calculateLineNumberNullable(source, field.offset), )); } @@ -1198,6 +1183,19 @@ List _validateProxyApi( return result; } +String? _findMatchingPrefixOrNull( + String value, { + required List prefixes, +}) { + for (final String prefix in prefixes) { + if (value.startsWith(prefix)) { + return prefix; + } + } + + return null; +} + class _FindInitializer extends dart_ast_visitor.RecursiveAstVisitor { dart_ast.Expression? initializer; @override From 33b2e07f91e131c3d625521d1839defff2901e1f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:10:20 -0500 Subject: [PATCH 13/15] avoid loops --- packages/pigeon/lib/generator_tools.dart | 11 +++++-- .../pigeon/test/generator_tools_test.dart | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 65f07b342450..be1a8bd6d92a 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -623,7 +623,8 @@ String? deducePackageName(String mainDartFile) { /// interfaces. /// /// This method assumes that all interfaces names can be found in -/// [allProxyApis]. Otherwise, throws an [ArgumentError]. +/// [allProxyApis] and an api doesn't contains itself as an interface. +/// Otherwise, throws an [ArgumentError]. Set recursiveFindAllInterfacesApis( AstProxyApi api, Iterable allProxyApis, @@ -638,14 +639,18 @@ Set recursiveFindAllInterfacesApis( if (interfacesApis.length != api.interfacesNames.length) { throw ArgumentError( - 'Could not find a ProxyApi for every interface name: ' + 'Could not find a valid ProxyApi for every interface name: ' '${api.interfacesNames}, ${allProxyApis.map((Api api) => api.name)}', ); } + // This removes the current api since it would be invalid for it to be a + // super class of itself. + final Set allProxyApisWithoutCurrent = + Set.from(allProxyApis)..remove(api); for (final AstProxyApi proxyApi in Set.from(interfacesApis)) { interfacesApis.addAll( - recursiveFindAllInterfacesApis(proxyApi, allProxyApis), + recursiveFindAllInterfacesApis(proxyApi, allProxyApisWithoutCurrent), ); } diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index bec70e52c5ad..344a98cd79f4 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -466,4 +466,35 @@ void main() { ]), ); }); + + test( + 'recursiveFindAllInterfacesApis throws error if api recursively implements itself', + () { + final AstProxyApi a = AstProxyApi( + name: 'A', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'B'}, + ); + final AstProxyApi b = AstProxyApi( + name: 'B', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'C'}, + ); + final AstProxyApi c = AstProxyApi( + name: 'C', + methods: [], + constructors: [], + fields: [], + interfacesNames: {'A'}, + ); + + expect( + () => recursiveFindAllInterfacesApis(a, [a, b, c]), + throwsArgumentError, + ); + }); } From c8db9c8bf0218f14a0d4e188fee9233b70873ec9 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:15:31 -0500 Subject: [PATCH 14/15] fix test --- packages/pigeon/test/dart_generator_test.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 63ca9c2a4a3b..4c96bd7d74af 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -1778,15 +1778,16 @@ name: foobar test('generate wrapResponse if is generating tests', () { final Root root = Root( apis: [ - Api( + AstHostApi( name: 'Api', - location: ApiLocation.host, dartHostTestHandler: 'ApiMock', methods: [ Method( - name: 'foo', - returnType: const TypeDeclaration.voidDeclaration(), - parameters: []) + name: 'foo', + location: ApiLocation.host, + returnType: const TypeDeclaration.voidDeclaration(), + parameters: [], + ) ]) ], classes: [], From b414381729ebc16d50a063f668051e1010821121 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:33:21 -0500 Subject: [PATCH 15/15] change superClass and interfaces to TypeDeclarations --- packages/pigeon/lib/ast.dart | 11 +- packages/pigeon/lib/generator_tools.dart | 109 +++++++------- packages/pigeon/lib/pigeon_lib.dart | 139 ++++++++++-------- packages/pigeon/pubspec.yaml | 2 +- .../pigeon/test/generator_tools_test.dart | 101 ++++++++----- 5 files changed, 208 insertions(+), 154 deletions(-) diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 51bcd215cccf..0c48faa45456 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -137,8 +137,8 @@ class AstProxyApi extends Api { super.documentationComments = const [], required this.constructors, required this.fields, - this.superClassName, - this.interfacesNames = const {}, + this.superClass, + this.interfaces = const {}, }); /// List of constructors inside the API. @@ -148,10 +148,10 @@ class AstProxyApi extends Api { List fields; /// Name of the class this class considers the super class. - final String? superClassName; + TypeDeclaration? superClass; /// Name of the classes this class considers to be implemented. - final Set interfacesNames; + Set interfaces; /// Methods implemented in the host platform language. Iterable get hostMethods => methods.where( @@ -181,7 +181,7 @@ class AstProxyApi extends Api { String toString() { return '(ProxyApi name:$name methods:$methods field:$fields ' 'documentationComments:$documentationComments ' - 'superClassName:$superClassName interfacesNames:$interfacesNames)'; + 'superClassName:$superClass interfacesNames:$interfaces)'; } } @@ -428,6 +428,7 @@ class NamedType extends Node { final List documentationComments; /// Returns a copy of [NamedType] instance with new attached [TypeDeclaration]. + @mustBeOverridden NamedType copyWithType(TypeDeclaration type) { return NamedType( name: name, diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 6f478dc39888..b9aec2b27994 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -622,39 +622,43 @@ String? deducePackageName(String mainDartFile) { /// Recursively search for all the interfaces apis from a list of names of /// interfaces. /// -/// This method assumes that all interfaces names can be found in -/// [allProxyApis] and an api doesn't contains itself as an interface. -/// Otherwise, throws an [ArgumentError]. -Set recursiveFindAllInterfacesApis( - AstProxyApi api, - Iterable allProxyApis, -) { - final Set interfacesApis = {}; - - for (final AstProxyApi proxyApi in allProxyApis) { - if (api.interfacesNames.contains(proxyApi.name)) { - interfacesApis.add(proxyApi); - } - } +/// This method assumes that all interfaces are ProxyApis and an api doesn't +/// contains itself as an interface. Otherwise, throws an [ArgumentError]. +Set recursiveFindAllInterfaceApis( + AstProxyApi api, { + Set seenApis = const {}, +}) { + final Set allInterfaces = {}; + + allInterfaces.addAll( + api.interfaces.map( + (TypeDeclaration type) { + if (!type.isProxyApi) { + throw ArgumentError( + 'Could not find a valid ProxyApi for an interface: $type', + ); + } else if (seenApis.contains(type.associatedProxyApi)) { + throw ArgumentError( + 'A ProxyApi cannot be a super class of itself: ${type.baseName}', + ); + } + return type.associatedProxyApi!; + }, + ), + ); - if (interfacesApis.length != api.interfacesNames.length) { - throw ArgumentError( - 'Could not find a valid ProxyApi for every interface name: ' - '${api.interfacesNames}, ${allProxyApis.map((Api api) => api.name)}', - ); - } + // Adds the current api since it would be invalid for it to be an interface + // of itself. + final Set newSeenApis = {...seenApis, api}; - // This removes the current api since it would be invalid for it to be a - // super class of itself. - final Set allProxyApisWithoutCurrent = - Set.from(allProxyApis)..remove(api); - for (final AstProxyApi proxyApi in Set.from(interfacesApis)) { - interfacesApis.addAll( - recursiveFindAllInterfacesApis(proxyApi, allProxyApisWithoutCurrent), - ); + for (final AstProxyApi interfaceApi in {...allInterfaces}) { + allInterfaces.addAll(recursiveFindAllInterfaceApis( + interfaceApi, + seenApis: newSeenApis, + )); } - return interfacesApis; + return allInterfaces; } /// Creates a list of ProxyApis where each `extends` the ProxyApi that follows @@ -665,45 +669,40 @@ Set recursiveFindAllInterfacesApis( /// This method assumes the super classes of each ProxyApi doesn't create a /// loop. Throws a [ArgumentError] if a loop is found. /// -/// This method also assumes that all super class names can be found in -/// [allProxyApis]. Otherwise, throws an [ArgumentError]. -List recursiveGetSuperClassApisChain( - AstProxyApi proxyApi, - Iterable allProxyApis, -) { - final List proxyApis = []; +/// This method also assumes that all super classes are ProxyApis. Otherwise, +/// throws an [ArgumentError]. +List recursiveGetSuperClassApisChain(AstProxyApi api) { + final List superClassChain = []; - String? currentProxyApiName = proxyApi.superClassName; - while (currentProxyApiName != null) { - if (proxyApis.length > allProxyApis.length) { - final Iterable apiNames = proxyApis.map( - (AstProxyApi api) => api.name, - ); + if (api.superClass != null && !api.superClass!.isProxyApi) { + throw ArgumentError( + 'Could not find a ProxyApi for super class: ${api.superClass!.baseName}', + ); + } + + AstProxyApi? currentProxyApi = api.superClass?.associatedProxyApi; + while (currentProxyApi != null) { + if (superClassChain.contains(currentProxyApi)) { throw ArgumentError( 'Loop found when processing super classes for a ProxyApi: ' - '${proxyApi.name},${apiNames.join(',')}', + '${api.name}, ${superClassChain.map((AstProxyApi api) => api.name)}', ); } - AstProxyApi? nextProxyApi; - for (final AstProxyApi node in allProxyApis) { - if (currentProxyApiName == node.name) { - nextProxyApi = node; - proxyApis.add(node); - } - } + superClassChain.add(currentProxyApi); - if (nextProxyApi == null) { + if (currentProxyApi.superClass != null && + !currentProxyApi.superClass!.isProxyApi) { throw ArgumentError( - 'Could not find a ProxyApi for every super class name: ' - '$currentProxyApiName, ${allProxyApis.map((Api api) => api.name)}', + 'Could not find a ProxyApi for super class: ' + '${currentProxyApi.superClass!.baseName}', ); } - currentProxyApiName = nextProxyApi.superClassName; + currentProxyApi = currentProxyApi.superClass?.associatedProxyApi; } - return proxyApis; + return superClassChain; } /// Enum to specify api type when generating code. diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index d5193f223ac1..187aa3466b2a 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -951,35 +951,24 @@ List _validateProxyApi( ); } - // Validate super class is another ProxyApi - final String? superClassName = api.superClassName; - if (api.superClassName != null && - !proxyApis.any((AstProxyApi api) => api.name == superClassName)) { - result.add(Error( - message: - 'Super class of ${api.name} is not marked as a @ProxyApi: $superClassName', - )); - } + AstProxyApi? directSuperClass; - // Validate all interfaces are other ProxyApis - for (final String interfaceName in api.interfacesNames) { - if (!proxyApis.any((AstProxyApi api) => api.name == interfaceName)) { - result.add(Error( - message: - 'Interface of ${api.name} is not marked as a @ProxyApi: $interfaceName', - )); + // Validate direct super class is another ProxyApi + if (api.superClass != null) { + directSuperClass = proxyApis.firstWhereOrNull( + (AstProxyApi proxyApi) => proxyApi.name == api.superClass?.baseName, + ); + if (directSuperClass == null) { + result.add( + Error( + message: 'Super class of ${api.name} is not marked as a @ProxyApi: ' + '${api.superClass?.baseName}', + ), + ); } } - List? superClassChain; - try { - superClassChain = recursiveGetSuperClassApisChain(api, proxyApis); - } catch (error) { - result.add(Error(message: error.toString())); - } - // Validate that the api does not inherit an unattached field from its super class. - final AstProxyApi? directSuperClass = superClassChain?.firstOrNull; if (directSuperClass != null && directSuperClass.unattachedFields.isNotEmpty) { result.add(Error( @@ -992,6 +981,19 @@ List _validateProxyApi( )); } + // Validate all interfaces are other ProxyApis + final Iterable interfaceNames = api.interfaces.map( + (TypeDeclaration type) => type.baseName, + ); + for (final String interfaceName in interfaceNames) { + if (!proxyApis.any((AstProxyApi api) => api.name == interfaceName)) { + result.add(Error( + message: + 'Interface of ${api.name} is not marked as a @ProxyApi: $interfaceName', + )); + } + } + final bool hasUnattachedField = api.unattachedFields.isNotEmpty; final bool hasRequiredFlutterMethod = api.flutterMethods.any((Method method) => method.isRequired); @@ -1032,7 +1034,10 @@ List _validateProxyApi( api.constructors.isEmpty && api.fields.isEmpty; if (!isValidInterfaceProxyApi) { - for (final String interfaceName in proxyApi.interfacesNames) { + final Iterable interfaceNames = proxyApi.interfaces.map( + (TypeDeclaration type) => type.baseName, + ); + for (final String interfaceName in interfaceNames) { if (interfaceName == api.name) { result.add(Error( message: @@ -1283,44 +1288,34 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } for (final Class classDefinition in referencedClasses) { - final List fields = []; - for (final NamedType field in classDefinition.fields) { - fields.add(field.copyWithType( - _attachAssociatedDefinition(field.type), - )); - } - classDefinition.fields = fields; + classDefinition.fields = _attachAssociatedDefinitions( + classDefinition.fields, + ); } for (final Api api in _apis) { for (final Method func in api.methods) { - final List paramList = []; - for (final Parameter param in func.parameters) { - paramList.add(param.copyWithType( - _attachAssociatedDefinition(param.type), - )); - } - func.parameters = paramList; + func.parameters = _attachAssociatedDefinitions(func.parameters); func.returnType = _attachAssociatedDefinition(func.returnType); } if (api is AstProxyApi) { for (final Constructor constructor in api.constructors) { - final List paramList = []; - for (final Parameter param in constructor.parameters) { - paramList.add( - param.copyWithType(_attachAssociatedDefinition(param.type)), - ); - } - constructor.parameters = paramList; + constructor.parameters = _attachAssociatedDefinitions( + constructor.parameters, + ); } - final List fieldList = []; - for (final ApiField field in api.fields) { - fieldList.add(field.copyWithType( - _attachAssociatedDefinition(field.type), - )); + api.fields = _attachAssociatedDefinitions(api.fields); + + if (api.superClass != null) { + api.superClass = _attachAssociatedDefinition(api.superClass!); + } + + final Set newInterfaceSet = {}; + for (final TypeDeclaration interface in api.interfaces) { + newInterfaceSet.add(_attachAssociatedDefinition(interface)); } - api.fields = fieldList; + api.interfaces = newInterfaceSet; } } @@ -1352,6 +1347,16 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { return type; } + List _attachAssociatedDefinitions(Iterable types) { + final List result = []; + for (final NamedType type in types) { + result.add( + type.copyWithType(_attachAssociatedDefinition(type.type)) as T, + ); + } + return result; + } + Object _expressionToMap(dart_ast.Expression expression) { if (expression is dart_ast.MethodInvocation) { final Map result = {}; @@ -1497,6 +1502,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } final String? superClassName = annotationMap['superClass'] as String?; + TypeDeclaration? superClass; if (superClassName != null && node.extendsClause != null) { _errors.add( Error( @@ -1505,6 +1511,27 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { lineNumber: _calculateLineNumber(source, node.offset), ), ); + } else if (superClassName != null) { + superClass = TypeDeclaration( + baseName: superClassName, + isNullable: false, + ); + } else if (node.extendsClause != null) { + superClass = TypeDeclaration( + baseName: node.extendsClause!.superclass.name2.lexeme, + isNullable: false, + ); + } + + final Set interfaces = {}; + if (node.implementsClause != null) { + for (final dart_ast.NamedType type + in node.implementsClause!.interfaces) { + interfaces.add(TypeDeclaration( + baseName: type.name2.lexeme, + isNullable: false, + )); + } } _currentApi = AstProxyApi( @@ -1512,12 +1539,8 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { methods: [], constructors: [], fields: [], - superClassName: - superClassName ?? node.extendsClause?.superclass.name2.lexeme, - interfacesNames: node.implementsClause?.interfaces - .map((dart_ast.NamedType type) => type.name2.lexeme) - .toSet() ?? - {}, + superClass: superClass, + interfaces: interfaces, documentationComments: _documentationCommentsParser(node.documentationComment?.tokens), ); diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 1f001eae6cb5..e077f7d81324 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: analyzer: ">=5.13.0 <7.0.0" args: ^2.1.0 collection: ^1.15.0 - meta: ^1.7.0 + meta: ^1.9.0 path: ^1.8.0 yaml: ^3.1.1 diff --git a/packages/pigeon/test/generator_tools_test.dart b/packages/pigeon/test/generator_tools_test.dart index 344a98cd79f4..6b43b93eecc5 100644 --- a/packages/pigeon/test/generator_tools_test.dart +++ b/packages/pigeon/test/generator_tools_test.dart @@ -375,31 +375,36 @@ void main() { }); test('recursiveGetSuperClassApisChain', () { - final AstProxyApi api = AstProxyApi( - name: 'Api', + final AstProxyApi superClassOfSuperClassApi = AstProxyApi( + name: 'Api3', methods: [], constructors: [], fields: [], - superClassName: 'Api2', ); final AstProxyApi superClassApi = AstProxyApi( name: 'Api2', methods: [], constructors: [], fields: [], - superClassName: 'Api3', + superClass: TypeDeclaration( + baseName: 'Api3', + isNullable: false, + associatedProxyApi: superClassOfSuperClassApi, + ), ); - final AstProxyApi superClassOfSuperClassApi = AstProxyApi( - name: 'Api3', + final AstProxyApi api = AstProxyApi( + name: 'Api', methods: [], constructors: [], fields: [], + superClass: TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: superClassApi, + ), ); - final List apiChain = recursiveGetSuperClassApisChain( - api, - [superClassOfSuperClassApi, api, superClassApi], - ); + final List apiChain = recursiveGetSuperClassApisChain(api); expect( apiChain, @@ -411,50 +416,69 @@ void main() { }); test('recursiveFindAllInterfacesApis', () { - final AstProxyApi api = AstProxyApi( - name: 'Api', + final AstProxyApi interfaceOfInterfaceApi2 = AstProxyApi( + name: 'Api5', methods: [], constructors: [], fields: [], - interfacesNames: {'Api2', 'Api3'}, ); - final AstProxyApi interfaceApi = AstProxyApi( - name: 'Api2', + final AstProxyApi interfaceOfInterfaceApi = AstProxyApi( + name: 'Api4', methods: [], constructors: [], fields: [], - interfacesNames: {'Api4', 'Api5'}, ); final AstProxyApi interfaceApi2 = AstProxyApi( name: 'Api3', methods: [], constructors: [], fields: [], - interfacesNames: {'Api5'}, + interfaces: { + TypeDeclaration( + baseName: 'Api5', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi2, + ), + }, ); - final AstProxyApi interfaceOfInterfaceApi = AstProxyApi( - name: 'Api4', + final AstProxyApi interfaceApi = AstProxyApi( + name: 'Api2', methods: [], constructors: [], fields: [], + interfaces: { + TypeDeclaration( + baseName: 'Api4', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi, + ), + TypeDeclaration( + baseName: 'Api5', + isNullable: false, + associatedProxyApi: interfaceOfInterfaceApi2, + ), + }, ); - final AstProxyApi interfaceOfInterfaceApi2 = AstProxyApi( - name: 'Api5', + final AstProxyApi api = AstProxyApi( + name: 'Api', methods: [], constructors: [], fields: [], + interfaces: { + TypeDeclaration( + baseName: 'Api2', + isNullable: false, + associatedProxyApi: interfaceApi, + ), + TypeDeclaration( + baseName: 'Api3', + isNullable: false, + associatedProxyApi: interfaceApi2, + ), + }, ); - final Set allInterfaces = recursiveFindAllInterfacesApis( - api, - [ - api, - interfaceApi, - interfaceApi2, - interfaceOfInterfaceApi, - interfaceOfInterfaceApi2, - ], - ); + final Set allInterfaces = recursiveFindAllInterfaceApis(api); expect( allInterfaces, @@ -475,25 +499,32 @@ void main() { methods: [], constructors: [], fields: [], - interfacesNames: {'B'}, ); final AstProxyApi b = AstProxyApi( name: 'B', methods: [], constructors: [], fields: [], - interfacesNames: {'C'}, ); final AstProxyApi c = AstProxyApi( name: 'C', methods: [], constructors: [], fields: [], - interfacesNames: {'A'}, ); + a.interfaces = { + TypeDeclaration(baseName: 'B', isNullable: false, associatedProxyApi: b), + }; + b.interfaces = { + TypeDeclaration(baseName: 'C', isNullable: false, associatedProxyApi: c), + }; + c.interfaces = { + TypeDeclaration(baseName: 'A', isNullable: false, associatedProxyApi: a), + }; + expect( - () => recursiveFindAllInterfacesApis(a, [a, b, c]), + () => recursiveFindAllInterfaceApis(a), throwsArgumentError, ); });