Skip to content

[pigeon] Adds ProxyApi to AST generation #5861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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