Skip to content

Commit c0cb0ee

Browse files
[pigeon] Adds ProxyApi to AST generation (flutter#5861)
Part of flutter#134777. This adds the `@ProxyApi`, `@attached`, and `@static` annotations and parsing to create the new ProxyApi.
1 parent 95f2e50 commit c0cb0ee

21 files changed

+1911
-414
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 16.0.5
2+
3+
* Adds ProxyApi to AST generation.
4+
15
## 16.0.4
26

37
* [swift] Improve style of Swift output.

packages/pigeon/lib/ast.dart

Lines changed: 194 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ class Method extends Node {
2929
required this.name,
3030
required this.returnType,
3131
required this.parameters,
32+
required this.location,
33+
this.isRequired = true,
3234
this.isAsynchronous = false,
35+
this.isStatic = false,
3336
this.offset,
3437
this.objcSelector = '',
3538
this.swiftFunction = '',
@@ -68,6 +71,18 @@ class Method extends Node {
6871
/// For example: [" List of documentation comments, separated by line.", ...]
6972
List<String> documentationComments;
7073

74+
/// Where the implementation of this method is located, host or Flutter.
75+
ApiLocation location;
76+
77+
/// Whether this method is required to be implemented.
78+
///
79+
/// This flag is typically used to determine whether a callback method for
80+
/// a `ProxyApi` is nullable or not.
81+
bool isRequired;
82+
83+
/// Whether this is a static method of a ProxyApi.
84+
bool isStatic;
85+
7186
@override
7287
String toString() {
7388
final String objcSelectorStr =
@@ -78,29 +93,177 @@ class Method extends Node {
7893
}
7994
}
8095

81-
/// Represents a collection of [Method]s that are hosted on a given [location].
82-
class Api extends Node {
96+
/// Represents a collection of [Method]s that are implemented on the platform
97+
/// side.
98+
class AstHostApi extends Api {
99+
/// Parametric constructor for [AstHostApi].
100+
AstHostApi({
101+
required super.name,
102+
required super.methods,
103+
super.documentationComments = const <String>[],
104+
this.dartHostTestHandler,
105+
});
106+
107+
/// The name of the Dart test interface to generate to help with testing.
108+
String? dartHostTestHandler;
109+
110+
@override
111+
String toString() {
112+
return '(HostApi name:$name methods:$methods documentationComments:$documentationComments dartHostTestHandler:$dartHostTestHandler)';
113+
}
114+
}
115+
116+
/// Represents a collection of [Method]s that are hosted on the Flutter side.
117+
class AstFlutterApi extends Api {
118+
/// Parametric constructor for [AstFlutterApi].
119+
AstFlutterApi({
120+
required super.name,
121+
required super.methods,
122+
super.documentationComments = const <String>[],
123+
});
124+
125+
@override
126+
String toString() {
127+
return '(FlutterApi name:$name methods:$methods documentationComments:$documentationComments)';
128+
}
129+
}
130+
131+
/// Represents an API that wraps a native class.
132+
class AstProxyApi extends Api {
133+
/// Parametric constructor for [AstProxyApi].
134+
AstProxyApi({
135+
required super.name,
136+
required super.methods,
137+
super.documentationComments = const <String>[],
138+
required this.constructors,
139+
required this.fields,
140+
this.superClass,
141+
this.interfaces = const <TypeDeclaration>{},
142+
});
143+
144+
/// List of constructors inside the API.
145+
final List<Constructor> constructors;
146+
147+
/// List of fields inside the API.
148+
List<ApiField> fields;
149+
150+
/// Name of the class this class considers the super class.
151+
TypeDeclaration? superClass;
152+
153+
/// Name of the classes this class considers to be implemented.
154+
Set<TypeDeclaration> interfaces;
155+
156+
/// Methods implemented in the host platform language.
157+
Iterable<Method> get hostMethods => methods.where(
158+
(Method method) => method.location == ApiLocation.host,
159+
);
160+
161+
/// Methods implemented in Flutter.
162+
Iterable<Method> get flutterMethods => methods.where(
163+
(Method method) => method.location == ApiLocation.flutter,
164+
);
165+
166+
/// All fields that are attached.
167+
///
168+
/// See [attached].
169+
Iterable<ApiField> get attachedFields => fields.where(
170+
(ApiField field) => field.isAttached,
171+
);
172+
173+
/// All fields that are not attached.
174+
///
175+
/// See [attached].
176+
Iterable<ApiField> get unattachedFields => fields.where(
177+
(ApiField field) => !field.isAttached,
178+
);
179+
180+
@override
181+
String toString() {
182+
return '(ProxyApi name:$name methods:$methods field:$fields '
183+
'documentationComments:$documentationComments '
184+
'superClassName:$superClass interfacesNames:$interfaces)';
185+
}
186+
}
187+
188+
/// Represents a constructor for an API.
189+
class Constructor extends Method {
190+
/// Parametric constructor for [Constructor].
191+
Constructor({
192+
required super.name,
193+
required super.parameters,
194+
super.offset,
195+
super.swiftFunction = '',
196+
super.documentationComments = const <String>[],
197+
}) : super(
198+
returnType: const TypeDeclaration.voidDeclaration(),
199+
location: ApiLocation.host,
200+
);
201+
202+
@override
203+
String toString() {
204+
final String swiftFunctionStr =
205+
swiftFunction.isEmpty ? '' : ' swiftFunction:$swiftFunction';
206+
return '(Constructor name:$name parameters:$parameters $swiftFunctionStr documentationComments:$documentationComments)';
207+
}
208+
}
209+
210+
/// Represents a field of an API.
211+
class ApiField extends NamedType {
212+
/// Constructor for [ApiField].
213+
ApiField({
214+
required super.name,
215+
required super.type,
216+
super.offset,
217+
super.documentationComments,
218+
this.isAttached = false,
219+
this.isStatic = false,
220+
}) : assert(!isStatic || isAttached);
221+
222+
/// Whether this is an attached field for a [AstProxyApi].
223+
///
224+
/// See [attached].
225+
final bool isAttached;
226+
227+
/// Whether this is a static field of a [AstProxyApi].
228+
///
229+
/// A static field must also be attached. See [attached].
230+
final bool isStatic;
231+
232+
/// Returns a copy of [Parameter] instance with new attached [TypeDeclaration].
233+
@override
234+
ApiField copyWithType(TypeDeclaration type) {
235+
return ApiField(
236+
name: name,
237+
type: type,
238+
offset: offset,
239+
documentationComments: documentationComments,
240+
isAttached: isAttached,
241+
isStatic: isStatic,
242+
);
243+
}
244+
245+
@override
246+
String toString() {
247+
return '(Field name:$name type:$type isAttached:$isAttached '
248+
'isStatic:$isStatic documentationComments:$documentationComments)';
249+
}
250+
}
251+
252+
/// Represents a collection of [Method]s.
253+
sealed class Api extends Node {
83254
/// Parametric constructor for [Api].
84255
Api({
85256
required this.name,
86-
required this.location,
87257
required this.methods,
88-
this.dartHostTestHandler,
89258
this.documentationComments = const <String>[],
90259
});
91260

92261
/// The name of the API.
93262
String name;
94263

95-
/// Where the API's implementation is located, host or Flutter.
96-
ApiLocation location;
97-
98264
/// List of methods inside the API.
99265
List<Method> methods;
100266

101-
/// The name of the Dart test interface to generate to help with testing.
102-
String? dartHostTestHandler;
103-
104267
/// List of documentation comments, separated by line.
105268
///
106269
/// Lines should not include the comment marker itself, but should include any
@@ -110,7 +273,7 @@ class Api extends Node {
110273

111274
@override
112275
String toString() {
113-
return '(Api name:$name location:$location methods:$methods documentationComments:$documentationComments)';
276+
return '(Api name:$name methods:$methods documentationComments:$documentationComments)';
114277
}
115278
}
116279

@@ -123,6 +286,7 @@ class TypeDeclaration {
123286
required this.isNullable,
124287
this.associatedEnum,
125288
this.associatedClass,
289+
this.associatedProxyApi,
126290
this.typeArguments = const <TypeDeclaration>[],
127291
});
128292

@@ -132,6 +296,7 @@ class TypeDeclaration {
132296
isNullable = false,
133297
associatedEnum = null,
134298
associatedClass = null,
299+
associatedProxyApi = null,
135300
typeArguments = const <TypeDeclaration>[];
136301

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

326+
/// Whether the [TypeDeclaration] has an [associatedProxyApi].
327+
bool get isProxyApi => associatedProxyApi != null;
328+
329+
/// Associated [AstProxyApi], if any.
330+
final AstProxyApi? associatedProxyApi;
331+
161332
@override
162333
int get hashCode {
163334
// This has to be implemented because TypeDeclaration is used as a Key to a
@@ -207,11 +378,21 @@ class TypeDeclaration {
207378
);
208379
}
209380

381+
/// Returns duplicated `TypeDeclaration` with attached `associatedProxyApi` value.
382+
TypeDeclaration copyWithProxyApi(AstProxyApi proxyApiDefinition) {
383+
return TypeDeclaration(
384+
baseName: baseName,
385+
isNullable: isNullable,
386+
associatedProxyApi: proxyApiDefinition,
387+
typeArguments: typeArguments,
388+
);
389+
}
390+
210391
@override
211392
String toString() {
212393
final String typeArgumentsStr =
213394
typeArguments.isEmpty ? '' : 'typeArguments:$typeArguments';
214-
return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass)';
395+
return '(TypeDeclaration baseName:$baseName isNullable:$isNullable$typeArgumentsStr isEnum:$isEnum isClass:$isClass isProxyApi:$isProxyApi)';
215396
}
216397
}
217398

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

249430
/// Returns a copy of [NamedType] instance with new attached [TypeDeclaration].
431+
@mustBeOverridden
250432
NamedType copyWithType(TypeDeclaration type) {
251433
return NamedType(
252434
name: name,

packages/pigeon/lib/cpp_generator.dart

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,21 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
191191
Indent indent, {
192192
required String dartPackageName,
193193
}) {
194-
final bool hasHostApi = root.apis.any((Api api) =>
195-
api.methods.isNotEmpty && api.location == ApiLocation.host);
196-
final bool hasFlutterApi = root.apis.any((Api api) =>
197-
api.methods.isNotEmpty && api.location == ApiLocation.flutter);
194+
final bool hasHostApi = root.apis
195+
.whereType<AstHostApi>()
196+
.any((Api api) => api.methods.isNotEmpty);
197+
final bool hasFlutterApi = root.apis
198+
.whereType<AstFlutterApi>()
199+
.any((Api api) => api.methods.isNotEmpty);
198200

199201
_writeFlutterError(indent);
200202
if (hasHostApi) {
201-
_writeErrorOr(indent, friends: root.apis.map((Api api) => api.name));
203+
_writeErrorOr(
204+
indent,
205+
friends: root.apis
206+
.where((Api api) => api is AstFlutterApi || api is AstHostApi)
207+
.map((Api api) => api.name),
208+
);
202209
}
203210
if (hasFlutterApi) {
204211
// Nothing yet.
@@ -291,7 +298,8 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
291298
indent.writeln('friend class ${friend.name};');
292299
}
293300
}
294-
for (final Api api in root.apis) {
301+
for (final Api api in root.apis
302+
.where((Api api) => api is AstFlutterApi || api is AstHostApi)) {
295303
// TODO(gaaclarke): Find a way to be more precise with our
296304
// friendships.
297305
indent.writeln('friend class ${api.name};');
@@ -317,10 +325,9 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
317325
CppOptions generatorOptions,
318326
Root root,
319327
Indent indent,
320-
Api api, {
328+
AstFlutterApi api, {
321329
required String dartPackageName,
322330
}) {
323-
assert(api.location == ApiLocation.flutter);
324331
if (getCodecClasses(api, root).isNotEmpty) {
325332
_writeCodec(generatorOptions, root, indent, api);
326333
}
@@ -370,10 +377,9 @@ class CppHeaderGenerator extends StructuredGenerator<CppOptions> {
370377
CppOptions generatorOptions,
371378
Root root,
372379
Indent indent,
373-
Api api, {
380+
AstHostApi api, {
374381
required String dartPackageName,
375382
}) {
376-
assert(api.location == ApiLocation.host);
377383
if (getCodecClasses(api, root).isNotEmpty) {
378384
_writeCodec(generatorOptions, root, indent, api);
379385
}
@@ -823,10 +829,9 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
823829
CppOptions generatorOptions,
824830
Root root,
825831
Indent indent,
826-
Api api, {
832+
AstFlutterApi api, {
827833
required String dartPackageName,
828834
}) {
829-
assert(api.location == ApiLocation.flutter);
830835
if (getCodecClasses(api, root).isNotEmpty) {
831836
_writeCodec(generatorOptions, root, indent, api);
832837
}
@@ -937,10 +942,9 @@ class CppSourceGenerator extends StructuredGenerator<CppOptions> {
937942
CppOptions generatorOptions,
938943
Root root,
939944
Indent indent,
940-
Api api, {
945+
AstHostApi api, {
941946
required String dartPackageName,
942947
}) {
943-
assert(api.location == ApiLocation.host);
944948
if (getCodecClasses(api, root).isNotEmpty) {
945949
_writeCodec(generatorOptions, root, indent, api);
946950
}

0 commit comments

Comments
 (0)