diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart index 55bc5691a3..00fb2f45f5 100644 --- a/pkgs/ffigen/lib/src/code_generator/compound.dart +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart @@ -5,6 +5,7 @@ import '../code_generator.dart'; import '../config_provider.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../visitor/ast.dart'; import 'binding_string.dart'; @@ -58,6 +59,15 @@ abstract class Compound extends BindingType with HasLocalScope { return type.getCType(context); } + @override + ApiAvailability get computeAvailability { + var avail = ApiAvailability.alwaysAvailable; + for (final member in members) { + avail = avail.merge(member.type.computeAvailability); + } + return avail; + } + @override bool get isObjCImport => context.objCBuiltInFunctions.getBuiltInCompoundName(originalName) != null; diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index 1f818965bd..d71f84885c 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../visitor/ast.dart'; import 'scope.dart'; @@ -60,6 +61,18 @@ class FunctionType extends Type with HasLocalScope { return sb.toString(); } + @override + ApiAvailability get computeAvailability { + var avail = returnType.computeAvailability; + for (final param in parameters) { + avail = avail.merge(param.type.computeAvailability); + } + for (final param in varArgParameters) { + avail = avail.merge(param.type.computeAvailability); + } + return avail; + } + @override String getCType(Context context, {bool writeArgumentNames = true}) => _getTypeImpl( @@ -159,6 +172,9 @@ class NativeFunc extends Type { return _type as FunctionType; } + @override + ApiAvailability get computeAvailability => type.computeAvailability; + @override String getCType(Context context, {bool writeArgumentNames = true}) { final funcType = _type is FunctionType diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index da7d4b3940..ebc9519c82 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../strings.dart' as strings; import '../visitor/ast.dart'; @@ -149,6 +150,15 @@ class ObjCBlock extends BindingType with HasLocalScope { bool get hasListener => returnType == voidType; + @override + ApiAvailability get computeAvailability { + var avail = returnType.computeAvailability; + for (final param in params) { + avail = avail.merge(param.type.computeAvailability); + } + return avail; + } + String _blockType(Context context) { final argStr = params .map((param) { @@ -444,10 +454,13 @@ ref.pointer.ref.invoke.cast<${_helper.trampNatFnCType}>() context.rootObjCScope.addPrivate('_BlockingTrampoline'), ); + final availStr = computeAvailability.apiAvailableMacro; + final avail = availStr == null ? '' : '$availStr\n'; + return ''' -typedef ${returnType.getNativeType()} (^$listenerName)($declArgStr); -__attribute__((visibility("default"))) __attribute__((used)) +${avail}typedef ${returnType.getNativeType()} (^$listenerName)($declArgStr); +${avail}__attribute__((visibility("default"))) __attribute__((used)) $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { return ^void($argStr) { ${generateRetain('block')}; @@ -455,8 +468,8 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED { }; } -typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); -__attribute__((visibility("default"))) __attribute__((used)) +${avail}typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr); +${avail}__attribute__((visibility("default"))) __attribute__((used)) $listenerName $blockingWrapper( $blockingName block, $blockingName listenerBlock, DOBJC_Context* ctx) NS_RETURNS_RETAINED { @@ -495,10 +508,13 @@ $listenerName $blockingWrapper( final getterSel = '@selector(getDOBJCDartProtocolMethodForSelector:)'; final blkGetter = '(($block)$msgSend(target, $getterSel, sel))'; + final availStr = computeAvailability.apiAvailableMacro; + final avail = availStr == null ? '' : '$availStr\n'; + return ''' -typedef $ret (^$block)($argRecv); -__attribute__((visibility("default"))) __attribute__((used)) +${avail}typedef $ret (^$block)($argRecv); +${avail}__attribute__((visibility("default"))) __attribute__((used)) $ret $fnName(id target, $argRecv) { return $blkGetter($argPass); } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index 32bb152fac..b18a23f708 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -76,6 +76,9 @@ class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope { bool get unavailable => apiAvailability.availability == Availability.none; + @override + ApiAvailability get computeAvailability => apiAvailability; + @override BindingString toBindingString(Writer w) { final context = w.context; diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index 66f9d1c39f..ebe14f4e35 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -65,6 +65,9 @@ class ObjCProtocol extends BindingType with ObjCMethods, HasLocalScope { bool get unavailable => apiAvailability.availability == Availability.none; + @override + ApiAvailability get computeAvailability => apiAvailability; + @override BindingString toBindingString(Writer w) { final protocolClass = ObjCBuiltInFunctions.protocolClass.gen(context); diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index aeb4f77405..fff39356c5 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../visitor/ast.dart'; /// Represents a pointer. @@ -21,6 +22,9 @@ class PointerType extends Type { return PointerType._(child); } + @override + ApiAvailability get computeAvailability => child.computeAvailability; + @override Type get baseType => child.baseType; diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index c269099b92..4191ae0448 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../visitor/ast.dart'; import 'scope.dart'; @@ -132,6 +133,9 @@ abstract class Type extends AstNode { /// that default values aren't supported for this type, eg void. String? getDefaultValue(Context context) => null; + /// Returns the availability of this type and all its nested types. + ApiAvailability get computeAvailability => ApiAvailability.alwaysAvailable; + @override void visit(Visitation visitation) => visitation.visitType(this); @@ -242,6 +246,9 @@ abstract class BindingType extends NoLookUpBinding implements Type { @override bool get isObjCImport => false; + @override + ApiAvailability get computeAvailability => ApiAvailability.alwaysAvailable; + @override void visit(Visitation visitation) => visitation.visitBindingType(this); } diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart index 3bf9182fd4..07e2eb77de 100644 --- a/pkgs/ffigen/lib/src/code_generator/typealias.dart +++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../header_parser/sub_parsers/api_availability.dart'; import '../strings.dart' as strings; import '../visitor/ast.dart'; import 'binding_string.dart'; @@ -105,6 +106,9 @@ class Typealias extends BindingType { return pointee.type; } + @override + ApiAvailability get computeAvailability => type.computeAvailability; + @override BindingString toBindingString(Writer w) { assert(!isAnonymous); diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart index a5cdde0d78..e1f2db592c 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart @@ -21,14 +21,17 @@ class ApiAvailability { final PlatformAvailability? macos; late final Availability availability; + final ExternalVersions? _externalVersions; + + static final alwaysAvailable = ApiAvailability(externalVersions: null); ApiAvailability({ this.alwaysDeprecated = false, this.alwaysUnavailable = false, this.ios, this.macos, - required ExternalVersions? externalVersions, - }) { + ExternalVersions? externalVersions, + }) : _externalVersions = externalVersions { availability = _getAvailability(externalVersions); } @@ -152,6 +155,57 @@ class ApiAvailability { return "$checkOsVersion('$apiName', $args);"; } + ApiAvailability merge(ApiAvailability other) { + if (this == alwaysAvailable) return other; + if (other == alwaysAvailable) return this; + + return ApiAvailability( + alwaysDeprecated: alwaysDeprecated || other.alwaysDeprecated, + alwaysUnavailable: alwaysUnavailable || other.alwaysUnavailable, + ios: PlatformAvailability.merge(ios, other.ios), + macos: PlatformAvailability.merge(macos, other.macos), + externalVersions: _externalVersions ?? other._externalVersions, + ); + } + + String? get apiAvailableMacro { + if (alwaysUnavailable) return '__attribute__((unavailable))'; + if (alwaysDeprecated) return '__attribute__((deprecated))'; + + final platforms = _platforms; + if (platforms.isEmpty) return null; + + final attributes = []; + for (final platform in platforms) { + if (platform.unavailable) { + attributes.add( + 'availability(${platform.name!.toLowerCase()}, unavailable)', + ); + } else { + final platformAttrs = []; + if (platform.introduced != null) { + platformAttrs.add('introduced=${platform.introduced}'); + } + if (platform.deprecated != null) { + platformAttrs.add('deprecated=${platform.deprecated}'); + } + if (platform.obsoleted != null) { + platformAttrs.add('obsoleted=${platform.obsoleted}'); + } + if (platformAttrs.isNotEmpty) { + attributes.add( + // ignore: lines_longer_than_80_chars + 'availability(${platform.name!.toLowerCase()},${platformAttrs.join(',')})', + ); + } else { + attributes.add('availability(${platform.name!.toLowerCase()})'); + } + } + } + if (attributes.isEmpty) return null; + return attributes.map((a) => '__attribute__(($a))').join(' '); + } + @override String toString() => '''Availability { @@ -185,6 +239,53 @@ class PlatformAvailability { return deprecated! < obsoleted! ? deprecated : obsoleted; } + static PlatformAvailability? merge( + PlatformAvailability? a, + PlatformAvailability? b, + ) { + if (a == null) return b; + if (b == null) return a; + + Version? newIntroduced; + if (a.introduced == null) { + newIntroduced = b.introduced; + } else if (b.introduced == null) { + newIntroduced = a.introduced; + } else { + newIntroduced = a.introduced! > b.introduced! + ? a.introduced + : b.introduced; + } + + Version? newDeprecated; + if (a.deprecated == null) { + newDeprecated = b.deprecated; + } else if (b.deprecated == null) { + newDeprecated = a.deprecated; + } else { + newDeprecated = a.deprecated! < b.deprecated! + ? a.deprecated + : b.deprecated; + } + + Version? newObsoleted; + if (a.obsoleted == null) { + newObsoleted = b.obsoleted; + } else if (b.obsoleted == null) { + newObsoleted = a.obsoleted; + } else { + newObsoleted = a.obsoleted! < b.obsoleted! ? a.obsoleted : b.obsoleted; + } + + return PlatformAvailability( + name: a.name ?? b.name, + introduced: newIntroduced, + deprecated: newDeprecated, + obsoleted: newObsoleted, + unavailable: a.unavailable || b.unavailable, + ); + } + @visibleForTesting Availability getAvailability(Versions version) { if (unavailable) {