Skip to content

Commit 6d71f2d

Browse files
authored
Code generation for ObjC protocols (#1175)
1 parent eaee5f6 commit 6d71f2d

40 files changed

+3600
-6150
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
To silence the warning set config `silence-enum-warning` to `true`.
2424
- Rename ObjC interface methods that clash with type names. Fixes
2525
https://github.com/dart-lang/native/issues/1007.
26+
- Added support for implementing ObjC protocols from Dart. Use the
27+
`objc-protocols` config option to generate bindings for a protocol.
2628

2729
## 12.0.0
2830

pkgs/ffigen/README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,11 +666,12 @@ import:
666666
<tbody>
667667
<tr>
668668
<td>
669-
objc-interfaces
669+
objc-interfaces<br><br>objc-protocols
670670
</td>
671671
<td>
672-
Filters for interface declarations. This option works the same as other
673-
declaration filters like `functions` and `structs`.
672+
Filters for Objective C interface and protocol declarations. This option
673+
works the same as other declaration filters like `functions` and
674+
`structs`.
674675
</td>
675676
<td>
676677

@@ -687,17 +688,21 @@ objc-interfaces:
687688
rename:
688689
# Removes '_' prefix from interface names.
689690
'_(.*)': '$1'
691+
objc-protocols:
692+
include:
693+
# Generates bindings for a specific protocol.
694+
- MyProtocol
690695
```
691696

692697
</td>
693698
</tr>
694699

695700
<tr>
696701
<td>
697-
objc-interfaces -> module
702+
objc-interfaces -> module<br><br>objc-protocols -> module
698703
</td>
699704
<td>
700-
Adds a module prefix to the class name when loading the class
705+
Adds a module prefix to the interface/protocol name when loading it
701706
from the dylib. This is only relevent for ObjC headers that are generated
702707
wrappers for a Swift library. See example/swift for more information.
703708
</td>

pkgs/ffigen/ffigen.schema.json

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,28 @@
333333
"$ref": "#/$defs/memberRename"
334334
},
335335
"module": {
336-
"$ref": "#/$defs/objcInterfaceModule"
336+
"$ref": "#/$defs/objcModule"
337+
}
338+
}
339+
},
340+
"objc-protocols": {
341+
"type": "object",
342+
"additionalProperties": false,
343+
"properties": {
344+
"include": {
345+
"$ref": "#/$defs/fullMatchOrRegexpList"
346+
},
347+
"exclude": {
348+
"$ref": "#/$defs/fullMatchOrRegexpList"
349+
},
350+
"rename": {
351+
"$ref": "#/$defs/rename"
352+
},
353+
"member-rename": {
354+
"$ref": "#/$defs/memberRename"
355+
},
356+
"module": {
357+
"$ref": "#/$defs/objcModule"
337358
}
338359
}
339360
},
@@ -495,7 +516,7 @@
495516
"opaque"
496517
]
497518
},
498-
"objcInterfaceModule": {
519+
"objcModule": {
499520
"type": "object",
500521
"patternProperties": {
501522
".*": {

pkgs/ffigen/lib/src/code_generator.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export 'code_generator/native_type.dart';
1919
export 'code_generator/objc_block.dart';
2020
export 'code_generator/objc_built_in_functions.dart';
2121
export 'code_generator/objc_interface.dart';
22+
export 'code_generator/objc_methods.dart';
2223
export 'code_generator/objc_nullable.dart';
24+
export 'code_generator/objc_protocol.dart';
2325
export 'code_generator/pointer.dart';
2426
export 'code_generator/struct.dart';
2527
export 'code_generator/type.dart';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ enum BindingStringType {
2424
enum_,
2525
typeDef,
2626
objcInterface,
27+
objcProtocol,
2728
objcBlock,
2829
}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ abstract class Compound extends BindingType {
3434
bool get isStruct => compoundType == CompoundType.struct;
3535
bool get isUnion => compoundType == CompoundType.union;
3636

37+
ObjCBuiltInFunctions? objCBuiltInFunctions;
38+
3739
Compound({
3840
super.usr,
3941
super.originalName,
@@ -44,6 +46,7 @@ abstract class Compound extends BindingType {
4446
super.dartDoc,
4547
List<Member>? members,
4648
super.isInternal,
49+
this.objCBuiltInFunctions,
4750
}) : members = members ?? [];
4851

4952
factory Compound.fromType({
@@ -55,6 +58,7 @@ abstract class Compound extends BindingType {
5558
int? pack,
5659
String? dartDoc,
5760
List<Member>? members,
61+
required ObjCBuiltInFunctions objCBuiltInFunctions,
5862
}) {
5963
switch (type) {
6064
case CompoundType.struct:
@@ -66,6 +70,7 @@ abstract class Compound extends BindingType {
6670
pack: pack,
6771
dartDoc: dartDoc,
6872
members: members,
73+
objCBuiltInFunctions: objCBuiltInFunctions,
6974
);
7075
case CompoundType.union:
7176
return Union(
@@ -76,6 +81,7 @@ abstract class Compound extends BindingType {
7681
pack: pack,
7782
dartDoc: dartDoc,
7883
members: members,
84+
objCBuiltInFunctions: objCBuiltInFunctions,
7985
);
8086
}
8187
}
@@ -88,8 +94,17 @@ abstract class Compound extends BindingType {
8894
return type.getCType(w);
8995
}
9096

97+
bool get _isBuiltIn =>
98+
objCBuiltInFunctions?.isBuiltInInterface(originalName) ?? false;
99+
91100
@override
92101
BindingString toBindingString(Writer w) {
102+
final bindingType =
103+
isStruct ? BindingStringType.struct : BindingStringType.union;
104+
if (_isBuiltIn) {
105+
return BindingString(type: bindingType, string: '');
106+
}
107+
93108
final s = StringBuffer();
94109
final enclosingClassName = name;
95110
if (dartDoc != null) {
@@ -135,14 +150,12 @@ abstract class Compound extends BindingType {
135150
}
136151
s.write('}\n\n');
137152

138-
return BindingString(
139-
type: isStruct ? BindingStringType.struct : BindingStringType.union,
140-
string: s.toString());
153+
return BindingString(type: bindingType, string: s.toString());
141154
}
142155

143156
@override
144157
void addDependencies(Set<Binding> dependencies) {
145-
if (dependencies.contains(this)) return;
158+
if (dependencies.contains(this) || _isBuiltIn) return;
146159

147160
dependencies.add(this);
148161
for (final m in members) {
@@ -154,7 +167,7 @@ abstract class Compound extends BindingType {
154167
bool get isIncompleteCompound => isIncomplete;
155168

156169
@override
157-
String getCType(Writer w) => name;
170+
String getCType(Writer w) => _isBuiltIn ? '${w.objcPkgPrefix}.$name' : name;
158171

159172
@override
160173
String getNativeType({String varName = ''}) => '$originalName $varName';

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

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,37 @@
44

55
import 'package:ffigen/src/code_generator.dart';
66
import 'package:ffigen/src/config_provider/config_types.dart';
7+
import 'package:ffigen/src/header_parser/data.dart' show bindingsIndex;
78

89
import 'binding_string.dart';
910
import 'writer.dart';
1011

1112
class ObjCBlock extends BindingType {
1213
final Type returnType;
1314
final List<Type> argTypes;
14-
1515
Func? _wrapListenerBlock;
1616

17-
ObjCBlock({
18-
required String usr,
17+
factory ObjCBlock({
1918
required Type returnType,
2019
required List<Type> argTypes,
21-
}) : this._(
22-
usr: usr,
23-
name: _getBlockName(returnType, argTypes),
24-
returnType: returnType,
25-
argTypes: argTypes,
26-
);
20+
}) {
21+
final usr = _getBlockUsr(returnType, argTypes);
22+
23+
final oldBlock = bindingsIndex.getSeenObjCBlock(usr);
24+
if (oldBlock != null) {
25+
return oldBlock;
26+
}
27+
28+
final block = ObjCBlock._(
29+
usr: usr,
30+
name: _getBlockName(returnType, argTypes),
31+
returnType: returnType,
32+
argTypes: argTypes,
33+
);
34+
bindingsIndex.addObjCBlockToSeen(usr, block);
35+
36+
return block;
37+
}
2738

2839
ObjCBlock._({
2940
required String super.usr,
@@ -42,6 +53,17 @@ class ObjCBlock extends BindingType {
4253
type.toString().replaceAll(_illegalNameChar, '');
4354
static final _illegalNameChar = RegExp(r'[^0-9a-zA-Z]');
4455

56+
static String _getBlockUsr(Type returnType, List<Type> argTypes) {
57+
// Create a fake USR code for the block. This code is used to dedupe blocks
58+
// with the same signature.
59+
final usr = StringBuffer();
60+
usr.write('objcBlock: ${returnType.cacheKey()}');
61+
for (final type in argTypes) {
62+
usr.write(' ${type.cacheKey()}');
63+
}
64+
return usr.toString();
65+
}
66+
4567
bool get hasListener => returnType == voidType;
4668

4769
@override

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@ class ObjCBuiltInFunctions {
2222
static const newPointerBlock = ObjCImport('newPointerBlock');
2323
static const newClosureBlock = ObjCImport('newClosureBlock');
2424
static const getBlockClosure = ObjCImport('getBlockClosure');
25+
static const getProtocolMethodSignature =
26+
ObjCImport('getProtocolMethodSignature');
27+
static const getProtocol = ObjCImport('getProtocol');
2528
static const objectBase = ObjCImport('ObjCObjectBase');
2629
static const blockBase = ObjCImport('ObjCBlockBase');
30+
static const protocolMethod = ObjCImport('ObjCProtocolMethod');
31+
static const protocolListenableMethod =
32+
ObjCImport('ObjCProtocolListenableMethod');
33+
static const protocolBuilder = ObjCImport('ObjCProtocolBuilder');
34+
static const dartProxy = ObjCImport('DartProxy');
2735

2836
// Keep in sync with pkgs/objective_c/ffigen_objc.yaml.
2937
static const builtInInterfaces = {
@@ -45,12 +53,12 @@ class ObjCBuiltInFunctions {
4553
'NSMutableArray',
4654
'NSMutableData',
4755
'NSMutableDictionary',
56+
'NSMutableIndexSet',
4857
'NSMutableSet',
4958
'NSMutableString',
5059
'NSNotification',
5160
'NSNumber',
5261
'NSObject',
53-
'NSProgress',
5462
'NSProxy',
5563
'NSSet',
5664
'NSString',
@@ -59,9 +67,18 @@ class ObjCBuiltInFunctions {
5967
'NSValue',
6068
'Protocol',
6169
};
70+
static const builtInCompounds = {
71+
'NSFastEnumerationState',
72+
'NSRange',
73+
};
6274

75+
// TODO(https://github.com/dart-lang/native/issues/1173): Ideally this check
76+
// would be based on more than just the name.
6377
bool isBuiltInInterface(String name) =>
6478
!generateForPackageObjectiveC && builtInInterfaces.contains(name);
79+
bool isBuiltInCompound(String name) =>
80+
!generateForPackageObjectiveC && builtInCompounds.contains(name);
81+
bool isNSObject(String name) => name == 'NSObject';
6582

6683
// We need to load a separate instance of objc_msgSend for each signature. If
6784
// the return type is a struct, we need to use objc_msgSend_stret instead, and
@@ -96,6 +113,12 @@ class ObjCBuiltInFunctions {
96113
sel.addDependencies(dependencies);
97114
}
98115
}
116+
117+
static bool isInstanceType(Type type) {
118+
if (type is ObjCInstanceType) return true;
119+
final baseType = type.typealiasType;
120+
return baseType is ObjCNullable && baseType.child is ObjCInstanceType;
121+
}
99122
}
100123

101124
/// A function, global variable, or helper type defined in package:objective_c.

0 commit comments

Comments
 (0)