Skip to content

Commit cb09254

Browse files
authored
[ffigen] Support transitive categories of built-in types (#1930)
1 parent 7a72868 commit cb09254

13 files changed

+128
-15
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## 17.0.0-wip
22

33
- Use package:objective_c 5.0.0
4+
- Support transitive categories of built-in types:
5+
https://github.com/dart-lang/native/issues/1820
46

57
## 16.1.0
68

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,24 @@ class ObjCBuiltInFunctions {
128128
static const builtInProtocols = {
129129
'NSStreamDelegate',
130130
};
131+
@visibleForTesting
132+
static const builtInCategories = {
133+
'NSDataCreation',
134+
'NSExtendedArray',
135+
'NSExtendedData',
136+
'NSExtendedDate',
137+
'NSExtendedDictionary',
138+
'NSExtendedEnumerator',
139+
'NSExtendedMutableArray',
140+
'NSExtendedMutableData',
141+
'NSExtendedMutableDictionary',
142+
'NSExtendedMutableOrderedSet',
143+
'NSExtendedMutableSet',
144+
'NSExtendedOrderedSet',
145+
'NSExtendedSet',
146+
'NSNumberCreation',
147+
'NSStringExtensionMethods',
148+
};
131149

132150
// TODO(https://github.com/dart-lang/native/issues/1173): Ideally this check
133151
// would be based on more than just the name.
@@ -139,6 +157,8 @@ class ObjCBuiltInFunctions {
139157
!generateForPackageObjectiveC && builtInEnums.contains(name);
140158
bool isBuiltInProtocol(String name) =>
141159
!generateForPackageObjectiveC && builtInProtocols.contains(name);
160+
bool isBuiltInCategory(String name) =>
161+
!generateForPackageObjectiveC && builtInCategories.contains(name);
142162
static bool isNSObject(String name) => name == 'NSObject';
143163

144164
// We need to load a separate instance of objc_msgSend for each signature. If

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class ObjCCategory extends NoLookUpBinding with ObjCMethods {
3434
return method.returnsInstanceType && !parent.isObjCImport;
3535
}
3636

37+
@override
38+
bool get isObjCImport => builtInFunctions.isBuiltInCategory(originalName);
39+
3740
@override
3841
final ObjCBuiltInFunctions builtInFunctions;
3942

pkgs/ffigen/lib/src/header_parser/parser.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,9 @@ List<Binding> transformBindings(Config config, List<Binding> bindings) {
177177
visit(FixOverriddenMethodsVisitation(), bindings);
178178
visit(FillMethodDependenciesVisitation(), bindings);
179179

180-
final included =
181-
visit(ApplyConfigFiltersVisitation(config), bindings).included;
180+
final filterResults = visit(ApplyConfigFiltersVisitation(config), bindings);
181+
final directlyIncluded = filterResults.directlyIncluded;
182+
final included = directlyIncluded.union(filterResults.indirectlyIncluded);
182183

183184
final byValueCompounds = visit(FindByValueCompoundsVisitation(),
184185
FindByValueCompoundsVisitation.rootNodes(included))
@@ -189,9 +190,11 @@ List<Binding> transformBindings(Config config, List<Binding> bindings) {
189190

190191
final transitives =
191192
visit(FindTransitiveDepsVisitation(), included).transitives;
192-
final directTransitives =
193-
visit(FindDirectTransitiveDepsVisitation(config, included), included)
194-
.directTransitives;
193+
final directTransitives = visit(
194+
FindDirectTransitiveDepsVisitation(
195+
config, included, directlyIncluded),
196+
included)
197+
.directTransitives;
195198

196199
final finalBindings = visit(
197200
ListBindingsVisitation(

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@ import 'ast.dart';
99

1010
class ApplyConfigFiltersVisitation extends Visitation {
1111
final Config config;
12-
final _directlyIncluded = <Binding>{};
13-
final _superTypes = <Binding>{};
12+
final directlyIncluded = <Binding>{};
13+
final indirectlyIncluded = <Binding>{};
1414
ApplyConfigFiltersVisitation(this.config);
1515

16-
Set<Binding> get included => _directlyIncluded.union(_superTypes);
17-
1816
void _visitImpl(Binding node, DeclarationFilters filters) {
1917
node.visitChildren(visitor);
2018
if (node.originalName == '') return;
2119
if (config.usrTypeMappings.containsKey(node.usr)) return;
22-
if (filters.shouldInclude(node)) _directlyIncluded.add(node);
20+
if (filters.shouldInclude(node)) directlyIncluded.add(node);
2321
}
2422

2523
@override
@@ -45,9 +43,9 @@ class ApplyConfigFiltersVisitation extends Visitation {
4543
_visitImpl(node, config.objcInterfaces);
4644

4745
// If this node is included, include all its super types.
48-
if (_directlyIncluded.contains(node)) {
46+
if (directlyIncluded.contains(node)) {
4947
for (ObjCInterface? t = node; t != null; t = t.superType) {
50-
if (!_superTypes.add(t)) break;
48+
if (!indirectlyIncluded.add(t)) break;
5149
}
5250
}
5351
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ class FindTransitiveDepsVisitation extends Visitation {
2121
class FindDirectTransitiveDepsVisitation extends Visitation {
2222
final Config config;
2323
final Set<Binding> includes;
24+
final Set<Binding> directIncludes;
2425
final directTransitives = <Binding>{};
2526

26-
FindDirectTransitiveDepsVisitation(this.config, this.includes);
27+
FindDirectTransitiveDepsVisitation(
28+
this.config, this.includes, this.directIncludes);
2729

2830
void _visitImpl(Binding node, bool forceVisitChildren) {
2931
if (node.isObjCImport) return;
@@ -41,6 +43,12 @@ class FindDirectTransitiveDepsVisitation extends Visitation {
4143
// included. This ensures that super types of stubs are also stubs, rather
4244
// than being omitted like the rest of the stub's children.
4345
visitor.visit(node.superType);
46+
47+
// Visit the categories of built-in interfaces that have been explicitly
48+
// included. https://github.com/dart-lang/native/issues/1820
49+
if (node.isObjCImport && directIncludes.contains(node)) {
50+
visitor.visitAll(node.categories);
51+
}
4452
}
4553

4654
@override

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ class ListBindingsVisitation extends Visitation {
2323
final Set<Binding> directTransitives;
2424
final bindings = <Binding>{};
2525

26-
ListBindingsVisitation(
27-
this.config, this.includes, this.transitives, this.directTransitives);
26+
ListBindingsVisitation(this.config, this.includes,
27+
Set<Binding> indirectTransitives, this.directTransitives)
28+
: transitives = {...indirectTransitives, ...directTransitives};
2829

2930
void _add(Binding node) {
3031
node.visitChildren(visitor);

pkgs/ffigen/test/native_objc_test/category_config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ objc-interfaces:
77
include:
88
- Thing
99
- ChildOfThing
10+
- NSURL
1011
objc-categories:
1112
include:
1213
- Sub

pkgs/ffigen/test/native_objc_test/category_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,20 @@ void main() {
7373
NSString str2 = str.instancetypeMethod();
7474
expect(str2.toDartString(), 'Hello');
7575
});
76+
77+
test('Transitive category on built-in type', () {
78+
// Regression test for https://github.com/dart-lang/native/issues/1820.
79+
// Include transitive category of explicitly included buit-in type.
80+
expect(NSURL.alloc().extensionMethod(), 555);
81+
82+
// Don't include transitive category of built-in type that hasn't been
83+
// explicitly included.
84+
final bindings = File('test/native_objc_test/category_bindings.dart')
85+
.readAsStringSync();
86+
expect(bindings, isNot(contains('excludedExtensionMethod')));
87+
88+
// This method is from an NSObject extension, which shouldn't be included.
89+
expect(bindings, isNot(contains('autoContentAccessingProxy')));
90+
});
7691
});
7792
}

pkgs/ffigen/test/native_objc_test/category_test.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#import <Foundation/NSObject.h>
66
#import <Foundation/NSString.h>
7+
#import <Foundation/NSURL.h>
78

89
@interface Thing : NSObject {}
910
-(int32_t)add:(int32_t)x Y:(int32_t) y;
@@ -45,3 +46,11 @@
4546
+(NSString*)staticMethod;
4647
-(instancetype)instancetypeMethod;
4748
@end
49+
50+
@interface NSURL (NSURLCategory)
51+
-(int32_t)extensionMethod;
52+
@end
53+
54+
@interface NSString (NSStringCategory)
55+
-(int32_t)excludedExtensionMethod;
56+
@end

pkgs/ffigen/test/native_objc_test/category_test.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ -(instancetype)instancetypeMethod {
7070
return [self copy];
7171
}
7272
@end
73+
74+
@implementation NSURL (NSURLCategory)
75+
-(int32_t)extensionMethod {
76+
return 555;
77+
}
78+
@end
79+
80+
@implementation NSString (NSStringCategory)
81+
-(int32_t)excludedExtensionMethod {
82+
return 999;
83+
}
84+
@end

pkgs/objective_c/lib/objective_c.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,18 @@ export 'src/objective_c_bindings_generated.dart'
4545
NSEnumerationOptions,
4646
NSEnumerator,
4747
NSError,
48+
NSExtendedArray,
49+
NSExtendedData,
50+
NSExtendedDate,
51+
NSExtendedDictionary,
52+
NSExtendedEnumerator,
53+
NSExtendedMutableArray,
4854
NSExtendedMutableData,
55+
NSExtendedMutableDictionary,
56+
NSExtendedMutableOrderedSet,
57+
NSExtendedMutableSet,
58+
NSExtendedOrderedSet,
59+
NSExtendedSet,
4960
NSFastEnumerationState,
5061
NSIndexSet,
5162
NSInputStream,

pkgs/objective_c/test/interface_lists_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ void main() {
2020
late List<String> yamlStructs;
2121
late List<String> yamlEnums;
2222
late List<String> yamlProtocols;
23+
late List<String> yamlCategories;
2324

2425
setUpAll(() {
2526
final yaml =
@@ -47,6 +48,11 @@ void main() {
4748
.map<String>((dynamic i) => i as String)
4849
.toList()
4950
..sort();
51+
yamlCategories = ((yaml['objc-categories'] as YamlMap)['include']
52+
as YamlList)
53+
.map<String>((dynamic i) => i as String)
54+
.toList()
55+
..sort();
5056
});
5157

5258
test('ObjCBuiltInFunctions.builtInInterfaces', () {
@@ -65,6 +71,10 @@ void main() {
6571
expect(ObjCBuiltInFunctions.builtInProtocols, yamlProtocols);
6672
});
6773

74+
test('ObjCBuiltInFunctions.builtInCategories', () {
75+
expect(ObjCBuiltInFunctions.builtInCategories, yamlCategories);
76+
});
77+
6878
test('package:objective_c exports all the interfaces', () {
6979
final exportFile = File('lib/objective_c.dart').readAsStringSync();
7080
for (final intf in yamlInterfaces) {
@@ -95,6 +105,13 @@ void main() {
95105
}
96106
});
97107

108+
test('package:objective_c exports all the categories', () {
109+
final exportFile = File('lib/objective_c.dart').readAsStringSync();
110+
for (final category in yamlCategories) {
111+
expect(exportFile, contains(RegExp('\\W$category\\W')));
112+
}
113+
});
114+
98115
test('All code genned interfaces are included in the list', () {
99116
final classNameRegExp = RegExp(r'^class (\w+) ');
100117
final allClassNames = <String>[];
@@ -150,5 +167,18 @@ void main() {
150167
}
151168
expect(allProtocolNames, unorderedEquals(yamlProtocols));
152169
});
170+
171+
test('All code genned categories are included in the list', () {
172+
final categoryNameRegExp = RegExp(r'^extension (\w+) on \w+ {');
173+
final allCategoryNames = <String>[];
174+
for (final line in File('lib/src/objective_c_bindings_generated.dart')
175+
.readAsLinesSync()) {
176+
final match = categoryNameRegExp.firstMatch(line);
177+
if (match != null) {
178+
allCategoryNames.add(match[1]!);
179+
}
180+
}
181+
expect(allCategoryNames, unorderedEquals(yamlCategories));
182+
});
153183
});
154184
}

0 commit comments

Comments
 (0)