From 4f38030b723142003e8515b27fc8fbaca12d5a05 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 13 Feb 2025 14:15:19 +1100 Subject: [PATCH] More tests, and fix some edge cases --- .../src/code_generator/objc_interface.dart | 2 + .../lib/src/code_generator/objc_protocol.dart | 2 + pkgs/ffigen/lib/src/header_parser/parser.dart | 3 + .../sub_parsers/objcinterfacedecl_parser.dart | 6 +- .../sub_parsers/objcprotocoldecl_parser.dart | 7 +- .../lib/src/visitor/apply_config_filters.dart | 4 + .../ffigen/lib/src/visitor/list_bindings.dart | 12 +- .../native_objc_test/deprecated_test.dart | 306 ++++++++++++++++++ 8 files changed, 326 insertions(+), 16 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index e2abb67c47..26aa6c8a5b 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -20,6 +20,7 @@ class ObjCInterface extends BindingType with ObjCMethods { final protocols = []; final categories = []; final subtypes = []; + final bool unavailable; @override final ObjCBuiltInFunctions builtInFunctions; @@ -34,6 +35,7 @@ class ObjCInterface extends BindingType with ObjCMethods { String? lookupName, super.dartDoc, required this.builtInFunctions, + this.unavailable = false, }) : lookupName = lookupName ?? originalName, super(name: name ?? originalName) { classObject = ObjCInternalGlobal('_class_$originalName', diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index ebd138d45e..f3bce986e2 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -15,6 +15,7 @@ class ObjCProtocol extends BindingType with ObjCMethods { final ObjCInternalGlobal _protocolPointer; late final ObjCInternalGlobal _conformsTo; late final ObjCMsgSendFunc _conformsToMsgSend; + final bool unavailable; // Filled by ListBindingsVisitation. bool generateAsStub = false; @@ -29,6 +30,7 @@ class ObjCProtocol extends BindingType with ObjCMethods { String? lookupName, super.dartDoc, required this.builtInFunctions, + this.unavailable = false, }) : lookupName = lookupName ?? originalName, _protocolPointer = ObjCInternalGlobal( '_protocol_$originalName', diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index 56ed565fb7..8e518afc6a 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart @@ -195,6 +195,9 @@ List transformBindings(Config config, List bindings) { config, included, directlyIncluded), included) .directTransitives; + // print("\n\nDirectly Included:\n ${directlyIncluded.whereType().map((p) => '${p.name}').join('\n ')}"); + // print("\n\nTransitives:\n ${transitives.whereType().map((p) => '${p.name}').join('\n ')}"); + // print("\n\nDirect Transitives:\n ${directTransitives.whereType().map((p) => '${p.name}').join('\n ')}"); final finalBindings = visit( ListBindingsVisitation( diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart index e047611b73..680a646386 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart @@ -22,12 +22,7 @@ Type? parseObjCInterfaceDeclaration(clang_types.CXCursor cursor) { final itfUsr = cursor.usr(); final itfName = cursor.spelling(); final decl = Declaration(usr: itfUsr, originalName: itfName); - final report = getApiAvailability(cursor); - if (report.availability == Availability.none) { - _logger.info('Omitting deprecated interface $itfName'); - return null; - } _logger.fine('++++ Adding ObjC interface: ' 'Name: $itfName, ${cursor.completeStringRepr()}'); @@ -40,6 +35,7 @@ Type? parseObjCInterfaceDeclaration(clang_types.CXCursor cursor) { dartDoc: getCursorDocComment(cursor, fallbackComment: itfName, availability: report.dartDoc), builtInFunctions: objCBuiltInFunctions, + unavailable: report.availability == Availability.none, ); } diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart index 9ddd62add1..dc80c4301d 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart @@ -46,10 +46,6 @@ ObjCProtocol? parseObjCProtocolDeclaration(clang_types.CXCursor cursor) { } final report = getApiAvailability(cursor); - if (report.availability == Availability.none) { - _logger.info('Omitting deprecated protocol $name'); - return null; - } _logger.fine('++++ Adding ObjC protocol: ' 'Name: $name, ${cursor.completeStringRepr()}'); @@ -59,8 +55,9 @@ ObjCProtocol? parseObjCProtocolDeclaration(clang_types.CXCursor cursor) { originalName: name, name: config.objcProtocols.rename(decl), lookupName: applyModulePrefix(name, config.protocolModule(decl)), - dartDoc: getCursorDocComment(cursor, availability: report.dartDoc), + dartDoc: getCursorDocComment(cursor, fallbackComment: name, availability: report.dartDoc), builtInFunctions: objCBuiltInFunctions, + unavailable: report.availability == Availability.none, ); // Make sure to add the protocol to the index before parsing the AST, to break diff --git a/pkgs/ffigen/lib/src/visitor/apply_config_filters.dart b/pkgs/ffigen/lib/src/visitor/apply_config_filters.dart index 56b1f60403..352737ae9c 100644 --- a/pkgs/ffigen/lib/src/visitor/apply_config_filters.dart +++ b/pkgs/ffigen/lib/src/visitor/apply_config_filters.dart @@ -38,6 +38,8 @@ class ApplyConfigFiltersVisitation extends Visitation { @override void visitObjCInterface(ObjCInterface node) { + if (node.unavailable) return; + node.filterMethods( (m) => config.objcInterfaces.shouldIncludeMember(node, m.originalName)); _visitImpl(node, config.objcInterfaces); @@ -61,6 +63,8 @@ class ApplyConfigFiltersVisitation extends Visitation { @override void visitObjCProtocol(ObjCProtocol node) { + if (node.unavailable) return; + node.filterMethods((m) { // TODO(https://github.com/dart-lang/native/issues/1149): Support class // methods on protocols if there's a use case. For now filter them. We diff --git a/pkgs/ffigen/lib/src/visitor/list_bindings.dart b/pkgs/ffigen/lib/src/visitor/list_bindings.dart index 3c19a979c2..446fa99c44 100644 --- a/pkgs/ffigen/lib/src/visitor/list_bindings.dart +++ b/pkgs/ffigen/lib/src/visitor/list_bindings.dart @@ -60,12 +60,12 @@ class ListBindingsVisitation extends Visitation { @override void visitObjCInterface(ObjCInterface node) { - if (!_visitImpl( + bool omit = node.unavailable || !_visitImpl( node, config.includeTransitiveObjCInterfaces ? _IncludeBehavior.configOrTransitive - : _IncludeBehavior.configOnly) && - directTransitives.contains(node)) { + : _IncludeBehavior.configOnly); + if (omit && directTransitives.contains(node)) { node.generateAsStub = true; bindings.add(node); } @@ -80,12 +80,12 @@ class ListBindingsVisitation extends Visitation { @override void visitObjCProtocol(ObjCProtocol node) { - if (!_visitImpl( + bool omit = node.unavailable || !_visitImpl( node, config.includeTransitiveObjCProtocols ? _IncludeBehavior.configOrTransitive - : _IncludeBehavior.configOnly) && - directTransitives.contains(node)) { + : _IncludeBehavior.configOnly); + if (omit && directTransitives.contains(node)) { node.generateAsStub = true; bindings.add(node); } diff --git a/pkgs/ffigen/test/native_objc_test/deprecated_test.dart b/pkgs/ffigen/test/native_objc_test/deprecated_test.dart index 00518db69f..516e53fcf5 100644 --- a/pkgs/ffigen/test/native_objc_test/deprecated_test.dart +++ b/pkgs/ffigen/test/native_objc_test/deprecated_test.dart @@ -640,5 +640,311 @@ void main() { expect(bindings, isNot(contains('deprecatedUnnamedEnum'))); }); }); + + group('ios <=1.5, macos >=1.5', () { + late final String bindings; + setUpAll(() { + bindings = bindingsForVersion( + iosVers: Versions(max: Version(1, 5, 0)), + macosVers: Versions(min: Version(1, 5, 0)), + ); + }); + + test('interfaces', () { + expect(bindings, contains('DeprecatedInterface ')); + expect(bindings, contains('DeprecatedInterfaceMethods ')); + }); + + test('protocols', () { + expect(bindings, contains('DeprecatedProtocol ')); + expect(bindings, contains('DeprecatedProtocolMethods ')); + }); + + test('interface methods', () { + expect(bindings, contains('normalMethod')); + expect(bindings, contains('unavailableMac')); + expect(bindings, contains('unavailableIos')); + expect(bindings, isNot(contains('unavailableBoth'))); + expect(bindings, contains('depMac2')); + expect(bindings, contains('depMac3')); + expect(bindings, contains('depIos2')); + expect(bindings, contains('depIos2Mac2')); + expect(bindings, contains('depIos2Mac3')); + expect(bindings, contains('depIos3')); + expect(bindings, contains('depIos3Mac2')); + expect(bindings, contains('depIos3Mac3')); + expect(bindings, isNot(contains('alwaysDeprecated'))); + expect(bindings, isNot(contains('alwaysUnavailable'))); + }); + + test('interface properties', () { + expect(bindings, contains('get normalProperty')); + expect(bindings, contains('set normalProperty')); + expect(bindings, contains('get deprecatedProperty')); + expect(bindings, contains('set deprecatedProperty')); + }); + + test('protocol methods', () { + expect(bindings, contains('protNormalMethod')); + expect(bindings, contains('protUnavailableMac')); + expect(bindings, contains('protUnavailableIos')); + expect(bindings, isNot(contains('protUnavailableBoth'))); + expect(bindings, contains('protDepMac2')); + expect(bindings, contains('protDepMac3')); + expect(bindings, contains('protDepIos2')); + expect(bindings, contains('protDepIos2Mac2')); + expect(bindings, contains('protDepIos2Mac3')); + expect(bindings, contains('protDepIos3')); + expect(bindings, contains('protDepIos3Mac2')); + expect(bindings, contains('protDepIos3Mac3')); + expect(bindings, isNot(contains('protAlwaysDeprecated'))); + expect(bindings, isNot(contains('protAlwaysUnavailable'))); + }); + + test('protocol properties', () { + expect(bindings, contains('protNormalProperty')); + expect(bindings, contains('setProtNormalProperty')); + expect(bindings, contains('protDeprecatedProperty')); + expect(bindings, contains('setProtDeprecatedProperty')); + }); + + test('category methods', () { + expect(bindings, contains('catNormalMethod')); + expect(bindings, contains('catUnavailableMac')); + expect(bindings, contains('catUnavailableIos')); + expect(bindings, isNot(contains('catUnavailableBoth'))); + expect(bindings, contains('catDepMac2')); + expect(bindings, contains('catDepMac3')); + expect(bindings, contains('catDepIos2')); + expect(bindings, contains('catDepIos2Mac2')); + expect(bindings, contains('catDepIos2Mac3')); + expect(bindings, contains('catDepIos3')); + expect(bindings, contains('catDepIos3Mac2')); + expect(bindings, contains('catDepIos3Mac3')); + expect(bindings, isNot(contains('catAlwaysDeprecated'))); + expect(bindings, isNot(contains('catAlwaysUnavailable'))); + }); + + test('category properties', () { + expect(bindings, contains('get catNormalProperty')); + expect(bindings, contains('set catNormalProperty')); + expect(bindings, contains('get catDeprecatedProperty')); + expect(bindings, contains('set catDeprecatedProperty')); + }); + + test('functions', () { + expect(bindings, contains('normalFunction')); + expect(bindings, contains('deprecatedFunction')); + }); + + test('structs', () { + expect(bindings, contains('NormalStruct')); + expect(bindings, contains('DeprecatedStruct')); + }); + + test('unions', () { + expect(bindings, contains('NormalUnion')); + expect(bindings, contains('DeprecatedUnion')); + }); + + test('enums', () { + expect(bindings, contains('NormalEnum')); + expect(bindings, contains('DeprecatedEnum')); + }); + + test('unnamed enums', () { + expect(bindings, contains('normalUnnamedEnum')); + expect(bindings, contains('deprecatedUnnamedEnum')); + }); + + test('dart doc', () { + final trimmed = bindings.split('\n').map((l) => l.trim()).join('\n'); + + expect(trimmed, contains(''' +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +final class DeprecatedStruct extends ffi.Struct''')); + + expect(trimmed, contains(''' +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +final class DeprecatedUnion extends ffi.Union''')); + + expect(trimmed, contains(''' +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +enum DeprecatedEnum''')); + + expect(trimmed, contains(''' +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 + +const int deprecatedUnnamedEnum = 1; +''')); + + expect(trimmed, contains(''' +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +int deprecatedFunction()''')); + + expect(trimmed, contains(''' +/// DeprecatedInterface +/// +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +''')); + + expect(trimmed, contains(''' +/// depIos2Mac2 +/// +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +''')); + + expect(trimmed, contains(''' +/// DeprecatedProtocol +/// +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +''')); + + expect(trimmed, contains(''' +/// protDepIos3 +/// +/// iOS: introduced 1.0.0, deprecated 3.0.0 +''')); + + expect(trimmed, contains(''' +/// DeprecatedCategory +/// +/// iOS: introduced 1.0.0, deprecated 2.0.0 +/// macOS: introduced 1.0.0, deprecated 2.0.0 +''')); + + expect(trimmed, contains(''' +/// DeprecatedCategoryMethods +/// +/// iOS: introduced 2.0.0 +/// macOS: introduced 10.0.0 +''')); + }); + }); + + group('ios >=0.5 <=0.9, macos >=0.5 <=0.9', () { + late final String bindings; + setUpAll(() { + bindings = bindingsForVersion( + iosVers: Versions(min: Version(0, 5, 0), max: Version(0, 9, 0)), + macosVers: Versions(min: Version(0, 5, 0), max: Version(0, 9, 0)), + ); + }); + + test('interfaces', () { + expect(bindings, isNot(contains('DeprecatedInterface '))); + expect(bindings, contains('DeprecatedInterfaceMethods ')); + }); + + test('protocols', () { + expect(bindings, isNot(contains('DeprecatedProtocol '))); + expect(bindings, contains('DeprecatedProtocolMethods ')); + }); + + test('interface methods', () { + expect(bindings, contains('normalMethod')); + expect(bindings, contains('unavailableMac')); + expect(bindings, contains('unavailableIos')); + expect(bindings, isNot(contains('unavailableBoth'))); + expect(bindings, contains('depMac2')); + expect(bindings, contains('depMac3')); + expect(bindings, contains('depIos2')); + expect(bindings, isNot(contains('depIos2Mac2'))); + expect(bindings, isNot(contains('depIos2Mac3'))); + expect(bindings, contains('depIos3')); + expect(bindings, isNot(contains('depIos3Mac2'))); + expect(bindings, isNot(contains('depIos3Mac3'))); + expect(bindings, isNot(contains('alwaysDeprecated'))); + expect(bindings, isNot(contains('alwaysUnavailable'))); + }); + + test('interface properties', () { + expect(bindings, contains('get normalProperty')); + expect(bindings, contains('set normalProperty')); + expect(bindings, isNot(contains('get deprecatedProperty'))); + expect(bindings, isNot(contains('set deprecatedProperty'))); + }); + + test('protocol methods', () { + expect(bindings, contains('protNormalMethod')); + expect(bindings, contains('protUnavailableMac')); + expect(bindings, contains('protUnavailableIos')); + expect(bindings, isNot(contains('protUnavailableBoth'))); + expect(bindings, contains('protDepMac2')); + expect(bindings, contains('protDepMac3')); + expect(bindings, contains('protDepIos2')); + expect(bindings, isNot(contains('protDepIos2Mac2'))); + expect(bindings, isNot(contains('protDepIos2Mac3'))); + expect(bindings, contains('protDepIos3')); + expect(bindings, isNot(contains('protDepIos3Mac2'))); + expect(bindings, isNot(contains('protDepIos3Mac3'))); + expect(bindings, isNot(contains('protAlwaysDeprecated'))); + expect(bindings, isNot(contains('protAlwaysUnavailable'))); + }); + + test('protocol properties', () { + expect(bindings, contains('protNormalProperty')); + expect(bindings, contains('setProtNormalProperty')); + expect(bindings, isNot(contains('protDeprecatedProperty'))); + expect(bindings, isNot(contains('setProtDeprecatedProperty'))); + }); + + test('category methods', () { + expect(bindings, isNot(contains('catNormalMethod'))); + expect(bindings, isNot(contains('catUnavailableMac'))); + expect(bindings, isNot(contains('catUnavailableIos'))); + expect(bindings, isNot(contains('catUnavailableBoth'))); + expect(bindings, isNot(contains('catDepMac2'))); + expect(bindings, isNot(contains('catDepMac3'))); + expect(bindings, isNot(contains('catDepIos2'))); + expect(bindings, isNot(contains('catDepIos2Mac2'))); + expect(bindings, isNot(contains('catDepIos2Mac3'))); + expect(bindings, isNot(contains('catDepIos3'))); + expect(bindings, isNot(contains('catDepIos3Mac2'))); + expect(bindings, isNot(contains('catDepIos3Mac3'))); + expect(bindings, isNot(contains('catAlwaysDeprecated'))); + expect(bindings, isNot(contains('catAlwaysUnavailable'))); + }); + + test('category properties', () { + expect(bindings, isNot(contains('get catNormalProperty'))); + expect(bindings, isNot(contains('set catNormalProperty'))); + expect(bindings, isNot(contains('get catDeprecatedProperty'))); + expect(bindings, isNot(contains('set catDeprecatedProperty'))); + }); + + test('functions', () { + expect(bindings, contains('normalFunction')); + expect(bindings, isNot(contains('deprecatedFunction'))); + }); + + test('structs', () { + expect(bindings, contains('NormalStruct')); + expect(bindings, isNot(contains('DeprecatedStruct'))); + }); + + test('unions', () { + expect(bindings, contains('NormalUnion')); + expect(bindings, isNot(contains('DeprecatedUnion'))); + }); + + test('enums', () { + expect(bindings, contains('NormalEnum')); + expect(bindings, isNot(contains('DeprecatedEnum'))); + }); + + test('unnamed enums', () { + expect(bindings, contains('normalUnnamedEnum')); + expect(bindings, isNot(contains('deprecatedUnnamedEnum'))); + }); + }); }); }