Skip to content

Commit ddd2882

Browse files
authored
[ffigen] Split protocol building stuff into a $Builder class (#2708)
1 parent 85a5d14 commit ddd2882

File tree

13 files changed

+814
-247
lines changed

13 files changed

+814
-247
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
With this breaking change, also some defaults changed: (1) `@Native` bindings
1414
are now the default, and (2) struct/unions refered to by pointer will be
1515
generated as `Opaque` by default.
16+
- __Breaking change__: Change how ObjC protocols are generated, splitting the
17+
methods related to constructing instances into a separate `Foo$Builder` class.
18+
The protocol's instance methods are now directly invokable from the built
19+
object.
1620
- __Breaking change__: Minor breaking change in the way that ObjC interface
1721
methods are generated. Interface methods are now generated as extension
1822
methods instead of being part of the class. This shouldn't require any code

pkgs/ffigen/lib/src/code_generator/objc_methods.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ mixin ObjCMethods {
117117
String generateStaticMethodBindings(Writer w, ObjCInterface target) =>
118118
_generateMethods(w, target, true);
119119

120-
String generateInstanceMethodBindings(Writer w, ObjCInterface target) =>
120+
String generateInstanceMethodBindings(Writer w, BindingType target) =>
121121
_generateMethods(w, target, false);
122122

123-
String _generateMethods(Writer w, ObjCInterface target, bool? staticMethods) {
123+
String _generateMethods(Writer w, BindingType target, bool? staticMethods) {
124124
return [
125125
for (final m in methods)
126126
if (staticMethods == null || staticMethods == m.isClassMethod)
@@ -423,7 +423,10 @@ class ObjCMethod extends AstNode with HasLocalScope {
423423
return '${_paramToStr(context, params.first)}, {$named}';
424424
}
425425

426-
String generateBindings(Writer w, ObjCInterface target) {
426+
String generateBindings(Writer w, BindingType target) {
427+
// Class methods are only supported for ObjCInterface targets.
428+
assert(target is ObjCInterface || !isClassMethod);
429+
427430
final context = w.context;
428431
final methodName = symbol.name;
429432
final upperName = methodName[0].toUpperCase() + methodName.substring(1);
@@ -446,7 +449,7 @@ class ObjCMethod extends AstNode with HasLocalScope {
446449
s.write('\n ${makeDartDoc(dartDoc)} ');
447450
late String targetStr;
448451
if (isClassMethod) {
449-
targetStr = target.classObject.name;
452+
targetStr = (target as ObjCInterface).classObject.name;
450453
switch (kind) {
451454
case ObjCMethodKind.method:
452455
s.write('static $returnTypeStr $methodName($paramStr)');

pkgs/ffigen/lib/src/code_generator/objc_protocol.dart

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,45 @@ interface class $name extends $protocolBase $impls{
106106
$name.castFromPointer($rawObjType other,
107107
{bool retain = false, bool release = false}) :
108108
this._(other, retain: retain, release: release);
109+
''');
110+
111+
if (!generateAsStub) {
112+
final msgSendInvoke = _conformsToMsgSend.invoke(
113+
context,
114+
'obj.ref.pointer',
115+
_conformsTo.name,
116+
[_protocolPointer.name],
117+
);
118+
119+
s.write('''
120+
121+
/// Returns whether [obj] is an instance of [$name].
122+
static bool conformsTo($objectBase obj) {
123+
return $msgSendInvoke;
124+
}
125+
''');
126+
}
127+
128+
s.write('''
129+
}
130+
131+
''');
132+
133+
if (!generateAsStub) {
134+
s.write('''
135+
extension $name\$Methods on $name {
136+
${generateInstanceMethodBindings(w, this)}
137+
}
109138
110139
''');
140+
}
111141

112142
if (!generateAsStub) {
143+
final builder = '$name\$Builder';
144+
s.write('''
145+
interface class $builder {
146+
''');
147+
113148
final buildArgs = <String>[];
114149
final buildImplementations = StringBuffer();
115150
final buildListenerImplementations = StringBuffer();
@@ -163,11 +198,11 @@ interface class $name extends $protocolBase $impls{
163198
}
164199

165200
buildImplementations.write('''
166-
$name.$fieldName.implement(builder, $argName);''');
201+
$builder.$fieldName.implement(builder, $argName);''');
167202
buildListenerImplementations.write('''
168-
$name.$fieldName.$maybeImplementAsListener(builder, $argName);''');
203+
$builder.$fieldName.$maybeImplementAsListener(builder, $argName);''');
169204
buildBlockingImplementations.write('''
170-
$name.$fieldName.$maybeImplementAsBlocking(builder, $argName);''');
205+
$builder.$fieldName.$maybeImplementAsBlocking(builder, $argName);''');
171206

172207
methodFields.write(makeDartDoc(method.dartDoc ?? method.originalName));
173208
methodFields.write('''static final $fieldName = $methodClass<$funcType>(
@@ -268,26 +303,14 @@ interface class $name extends $protocolBase $impls{
268303
''';
269304
}
270305

271-
final msgSendInvoke = _conformsToMsgSend.invoke(
272-
context,
273-
'obj.ref.pointer',
274-
_conformsTo.name,
275-
[_protocolPointer.name],
276-
);
277306
s.write('''
278-
/// Returns whether [obj] is an instance of [$name].
279-
static bool conformsTo($objectBase obj) {
280-
return $msgSendInvoke;
281-
}
282307
283308
$builders
284309
$listenerBuilders
285310
$methodFields
286-
''');
287-
}
288-
s.write('''
289311
}
290312
''');
313+
}
291314

292315
return BindingString(
293316
type: BindingStringType.objcProtocol,

pkgs/ffigen/lib/src/visitor/fill_method_dependencies.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class FillMethodDependenciesVisitation extends Visitation {
3131

3232
for (final method in node.methods) {
3333
method.fillProtocolBlock();
34+
method.fillMsgSend();
3435
}
3536
}
3637
}

pkgs/ffigen/test/native_objc_test/protocol_test.dart

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ void main() {
143143
test('Method implementation', () {
144144
final consumer = ProtocolConsumer();
145145

146-
final MyProtocol myProtocol = MyProtocol.implement(
146+
final MyProtocol myProtocol = MyProtocol$Builder.implement(
147147
instanceMethod_withDouble_: (NSString s, double x) {
148148
return 'MyProtocol: ${s.toDartString()}: $x'.toNSString();
149149
},
@@ -168,7 +168,7 @@ void main() {
168168
final consumer = ProtocolConsumer();
169169

170170
final protocolBuilder = ObjCProtocolBuilder();
171-
MyProtocol.addToBuilder(
171+
MyProtocol$Builder.addToBuilder(
172172
protocolBuilder,
173173
instanceMethod_withDouble_: (NSString s, double x) {
174174
return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
@@ -177,7 +177,7 @@ void main() {
177177
return s.y - s.x;
178178
},
179179
);
180-
SecondaryProtocol.addToBuilder(
180+
SecondaryProtocol$Builder.addToBuilder(
181181
protocolBuilder,
182182
otherMethod_b_c_d_: (int a, int b, int c, int d) {
183183
return a * b * c * d;
@@ -208,20 +208,18 @@ void main() {
208208
final consumer = ProtocolConsumer();
209209

210210
final protocolBuilder = ObjCProtocolBuilder();
211-
MyProtocol.instanceMethod_withDouble_.implement(protocolBuilder, (
212-
NSString s,
213-
double x,
214-
) {
215-
return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
216-
});
217-
SecondaryProtocol.otherMethod_b_c_d_.implement(protocolBuilder, (
218-
int a,
219-
int b,
220-
int c,
221-
int d,
222-
) {
223-
return a * b * c * d;
224-
});
211+
MyProtocol$Builder.instanceMethod_withDouble_.implement(
212+
protocolBuilder,
213+
(NSString s, double x) {
214+
return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
215+
},
216+
);
217+
SecondaryProtocol$Builder.otherMethod_b_c_d_.implement(
218+
protocolBuilder,
219+
(int a, int b, int c, int d) {
220+
return a * b * c * d;
221+
},
222+
);
225223
final protocolImpl = protocolBuilder.build();
226224
final MyProtocol asMyProtocol = MyProtocol.castFrom(protocolImpl);
227225
final SecondaryProtocol asSecondaryProtocol =
@@ -239,7 +237,7 @@ void main() {
239237
test('Unimplemented method', () {
240238
final consumer = ProtocolConsumer();
241239

242-
final MyProtocol myProtocol = MyProtocol.implement(
240+
final MyProtocol myProtocol = MyProtocol$Builder.implement(
243241
instanceMethod_withDouble_: (NSString s, double x) {
244242
throw UnimplementedError();
245243
},
@@ -254,7 +252,7 @@ void main() {
254252
final consumer = ProtocolConsumer();
255253

256254
final listenerCompleter = Completer<int>();
257-
final MyProtocol myProtocol = MyProtocol.implementAsListener(
255+
final MyProtocol myProtocol = MyProtocol$Builder.implementAsListener(
258256
instanceMethod_withDouble_: (NSString s, double x) {
259257
return 'MyProtocol: ${s.toDartString()}: $x'.toNSString();
260258
},
@@ -284,7 +282,7 @@ void main() {
284282

285283
final listenerCompleter = Completer<int>();
286284
final protocolBuilder = ObjCProtocolBuilder();
287-
MyProtocol.addToBuilderAsListener(
285+
MyProtocol$Builder.addToBuilderAsListener(
288286
protocolBuilder,
289287
instanceMethod_withDouble_: (NSString s, double x) {
290288
return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
@@ -293,7 +291,7 @@ void main() {
293291
listenerCompleter.complete(x);
294292
},
295293
);
296-
SecondaryProtocol.addToBuilder(
294+
SecondaryProtocol$Builder.addToBuilder(
297295
protocolBuilder,
298296
otherMethod_b_c_d_: (int a, int b, int c, int d) {
299297
return a * b * c * d;
@@ -329,7 +327,7 @@ void main() {
329327
final consumer = ProtocolConsumer();
330328

331329
final listenerCompleter = Completer<int>();
332-
final MyProtocol myProtocol = MyProtocol.implementAsBlocking(
330+
final MyProtocol myProtocol = MyProtocol$Builder.implementAsBlocking(
333331
instanceMethod_withDouble_: (NSString s, double x) {
334332
throw UnimplementedError();
335333
},
@@ -352,7 +350,7 @@ void main() {
352350

353351
final listenerCompleter = Completer<int>();
354352
final protocolBuilder = ObjCProtocolBuilder();
355-
MyProtocol.addToBuilderAsBlocking(
353+
MyProtocol$Builder.addToBuilderAsBlocking(
356354
protocolBuilder,
357355
instanceMethod_withDouble_: (NSString s, double x) {
358356
throw UnimplementedError();
@@ -365,7 +363,7 @@ void main() {
365363
ptr.value = 98765;
366364
},
367365
);
368-
SecondaryProtocol.addToBuilder(
366+
SecondaryProtocol$Builder.addToBuilder(
369367
protocolBuilder,
370368
otherMethod_b_c_d_: (int a, int b, int c, int d) {
371369
return a * b * c * d;
@@ -389,7 +387,7 @@ void main() {
389387
final consumer = ProtocolConsumer();
390388

391389
final builder = ObjCProtocolBuilder();
392-
MyProtocol.instanceMethod_withDouble_.implementWithBlock(
390+
MyProtocol$Builder.instanceMethod_withDouble_.implementWithBlock(
393391
builder,
394392
ObjCBlock_NSString_ffiVoid_NSString_ffiDouble.fromFunction(
395393
(Pointer<Void> _, NSString s, double x) =>
@@ -440,18 +438,18 @@ void main() {
440438

441439
test('Unused protocol', () {
442440
// Regression test for https://github.com/dart-lang/native/issues/1672.
443-
final proto = UnusedProtocol.implement(someMethod: () => 123);
441+
final proto = UnusedProtocol$Builder.implement(someMethod: () => 123);
444442
expect(proto, isNotNull);
445443
});
446444

447445
test('Disabled method', () {
448446
// Regression test for https://github.com/dart-lang/native/issues/1702.
449-
expect(MyProtocol.instanceMethod_withDouble_.isAvailable, isTrue);
450-
expect(MyProtocol.optionalMethod_.isAvailable, isTrue);
451-
expect(MyProtocol.disabledMethod.isAvailable, isFalse);
447+
expect(MyProtocol$Builder.instanceMethod_withDouble_.isAvailable, isTrue);
448+
expect(MyProtocol$Builder.optionalMethod_.isAvailable, isTrue);
449+
expect(MyProtocol$Builder.disabledMethod.isAvailable, isFalse);
452450

453451
expect(
454-
() => MyProtocol.disabledMethod.implement(
452+
() => MyProtocol$Builder.disabledMethod.implement(
455453
ObjCProtocolBuilder(),
456454
() => 123,
457455
),
@@ -474,7 +472,9 @@ void main() {
474472
int count = 0;
475473

476474
final protocolBuilder = ObjCProtocolBuilder();
477-
MyProtocol.voidMethod_.implementAsListener(protocolBuilder, (int x) {
475+
MyProtocol$Builder.voidMethod_.implementAsListener(protocolBuilder, (
476+
int x,
477+
) {
478478
expect(x, 123);
479479
++count;
480480
if (count == 1000) completer.complete();
@@ -497,7 +497,7 @@ void main() {
497497
final block = InstanceMethodBlock.fromFunction(
498498
(Pointer<Void> p, NSString s, double x) => 'Hello'.toNSString(),
499499
);
500-
MyProtocol.instanceMethod_withDouble_.implementWithBlock(
500+
MyProtocol$Builder.instanceMethod_withDouble_.implementWithBlock(
501501
protocolBuilder,
502502
block,
503503
);
@@ -655,7 +655,7 @@ void main() {
655655
test('adding more methods after build', () {
656656
final protocolBuilder = ObjCProtocolBuilder();
657657

658-
MyProtocol.addToBuilder(
658+
MyProtocol$Builder.addToBuilder(
659659
protocolBuilder,
660660
instanceMethod_withDouble_: (NSString s, double x) {
661661
return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString();
@@ -668,7 +668,7 @@ void main() {
668668
final protocolImpl = protocolBuilder.build();
669669

670670
expect(
671-
() => SecondaryProtocol.addToBuilder(
671+
() => SecondaryProtocol$Builder.addToBuilder(
672672
protocolBuilder,
673673
otherMethod_b_c_d_: (int a, int b, int c, int d) {
674674
return a * b * c * d;
@@ -677,5 +677,16 @@ void main() {
677677
throwsA(isA<StateError>()),
678678
);
679679
});
680+
681+
test('calling methods on a protocol instance', () {
682+
final protoImpl = ObjCProtocolImpl();
683+
684+
final MyProtocol myProto = protoImpl.returnsMyProtocol();
685+
final result = myProto.instanceMethod(
686+
"abc".toNSString(),
687+
withDouble: 123,
688+
);
689+
expect(result.toDartString(), 'ObjCProtocolImpl: abc: 123.00');
690+
});
680691
});
681692
}

pkgs/ffigen/test/native_objc_test/protocol_test.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ typedef struct {
6363
@optional
6464
- (nullable instancetype)returnsInstanceType;
6565

66+
@optional
67+
- (id<MyProtocol>)returnsMyProtocol;
68+
6669
@end
6770

6871
@protocol EmptyProtocol

pkgs/ffigen/test/native_objc_test/protocol_test.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ + (int32_t)optionalClassMethod {
8383
return 5432;
8484
}
8585

86+
- (id<MyProtocol>)returnsMyProtocol {
87+
return self;
88+
}
89+
8690
@end
8791

8892

0 commit comments

Comments
 (0)