Skip to content

Commit

Permalink
[pigeon] Adds ProxyApi to AST generation (flutter#5861)
Browse files Browse the repository at this point in the history
Part of flutter/flutter#134777.

This adds the `@ProxyApi`, `@attached`, and `@static` annotations and parsing to create the new ProxyApi.
  • Loading branch information
bparrishMines authored Jan 30, 2024
1 parent 95f2e50 commit c0cb0ee
Show file tree
Hide file tree
Showing 21 changed files with 1,911 additions and 414 deletions.
4 changes: 4 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 16.0.5

* Adds ProxyApi to AST generation.

## 16.0.4

* [swift] Improve style of Swift output.
Expand Down
206 changes: 194 additions & 12 deletions packages/pigeon/lib/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class Method extends Node {
required this.name,
required this.returnType,
required this.parameters,
required this.location,
this.isRequired = true,
this.isAsynchronous = false,
this.isStatic = false,
this.offset,
this.objcSelector = '',
this.swiftFunction = '',
Expand Down Expand Up @@ -68,6 +71,18 @@ class Method extends Node {
/// For example: [" List of documentation comments, separated by line.", ...]
List<String> 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 isRequired;

/// Whether this is a static method of a ProxyApi.
bool isStatic;

@override
String toString() {
final String objcSelectorStr =
Expand All @@ -78,29 +93,177 @@ 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 <String>[],
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 <String>[],
});

@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 <String>[],
required this.constructors,
required this.fields,
this.superClass,
this.interfaces = const <TypeDeclaration>{},
});

/// List of constructors inside the API.
final List<Constructor> constructors;

/// List of fields inside the API.
List<ApiField> fields;

/// Name of the class this class considers the super class.
TypeDeclaration? superClass;

/// Name of the classes this class considers to be implemented.
Set<TypeDeclaration> interfaces;

/// Methods implemented in the host platform language.
Iterable<Method> get hostMethods => methods.where(
(Method method) => method.location == ApiLocation.host,
);

/// Methods implemented in Flutter.
Iterable<Method> get flutterMethods => methods.where(
(Method method) => method.location == ApiLocation.flutter,
);

/// All fields that are attached.
///
/// See [attached].
Iterable<ApiField> get attachedFields => fields.where(
(ApiField field) => field.isAttached,
);

/// All fields that are not attached.
///
/// See [attached].
Iterable<ApiField> get unattachedFields => fields.where(
(ApiField field) => !field.isAttached,
);

@override
String toString() {
return '(ProxyApi name:$name methods:$methods field:$fields '
'documentationComments:$documentationComments '
'superClassName:$superClass interfacesNames:$interfaces)';
}
}

/// Represents a constructor for an API.
class Constructor extends Method {
/// Parametric constructor for [Constructor].
Constructor({
required super.name,
required super.parameters,
super.offset,
super.swiftFunction = '',
super.documentationComments = const <String>[],
}) : super(
returnType: const TypeDeclaration.voidDeclaration(),
location: ApiLocation.host,
);

@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 ApiField extends NamedType {
/// Constructor for [ApiField].
ApiField({
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
ApiField copyWithType(TypeDeclaration type) {
return ApiField(
name: name,
type: type,
offset: offset,
documentationComments: documentationComments,
isAttached: isAttached,
isStatic: isStatic,
);
}

@override
String toString() {
return '(Field name:$name type:$type isAttached:$isAttached '
'isStatic:$isStatic documentationComments:$documentationComments)';
}
}

/// 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 <String>[],
});

/// 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<Method> 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
Expand All @@ -110,7 +273,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)';
}
}

Expand All @@ -123,6 +286,7 @@ class TypeDeclaration {
required this.isNullable,
this.associatedEnum,
this.associatedClass,
this.associatedProxyApi,
this.typeArguments = const <TypeDeclaration>[],
});

Expand All @@ -132,6 +296,7 @@ class TypeDeclaration {
isNullable = false,
associatedEnum = null,
associatedClass = null,
associatedProxyApi = null,
typeArguments = const <TypeDeclaration>[];

/// The base name of the [TypeDeclaration] (ex 'Foo' to 'Foo<Bar>?').
Expand All @@ -158,6 +323,12 @@ class TypeDeclaration {
/// Associated [Class], if any.
final Class? associatedClass;

/// 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
Expand Down Expand Up @@ -207,11 +378,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)';
}
}

Expand Down Expand Up @@ -247,6 +428,7 @@ class NamedType extends Node {
final List<String> documentationComments;

/// Returns a copy of [NamedType] instance with new attached [TypeDeclaration].
@mustBeOverridden
NamedType copyWithType(TypeDeclaration type) {
return NamedType(
name: name,
Expand Down
32 changes: 18 additions & 14 deletions packages/pigeon/lib/cpp_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,21 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
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<AstHostApi>()
.any((Api api) => api.methods.isNotEmpty);
final bool hasFlutterApi = root.apis
.whereType<AstFlutterApi>()
.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.
Expand Down Expand Up @@ -291,7 +298,8 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
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};');
Expand All @@ -317,10 +325,9 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
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);
}
Expand Down Expand Up @@ -370,10 +377,9 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
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);
}
Expand Down Expand Up @@ -823,10 +829,9 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
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);
}
Expand Down Expand Up @@ -937,10 +942,9 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
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);
}
Expand Down
Loading

0 comments on commit c0cb0ee

Please sign in to comment.