Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/compound.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/func_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down
28 changes: 22 additions & 6 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -444,19 +454,22 @@ 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')};
block(${retains.join(', ')});
};
}

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 {
Expand Down Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/objc_protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/pointer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -21,6 +22,9 @@ class PointerType extends Type {
return PointerType._(child);
}

@override
ApiAvailability get computeAvailability => child.computeAvailability;

@override
Type get baseType => child.baseType;

Expand Down
7 changes: 7 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/typealias.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -105,6 +106,9 @@ class Typealias extends BindingType {
return pointee.type;
}

@override
ApiAvailability get computeAvailability => type.computeAvailability;

@override
BindingString toBindingString(Writer w) {
assert(!isAnonymous);
Expand Down
105 changes: 103 additions & 2 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/api_availability.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ class ApiAvailability {
final PlatformAvailability? macos;

late final Availability availability;
final ExternalVersions? _externalVersions;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

externalVersions comes from the config, which is stored in the context. Rather than storing it here, you should pass Context to the merge method and get it from there. It will mean a bit more plumbing, but it's a cleaner solution.


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);
}

Expand Down Expand Up @@ -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 = <String>[];
for (final platform in platforms) {
if (platform.unavailable) {
attributes.add(
'availability(${platform.name!.toLowerCase()}, unavailable)',
);
} else {
final platformAttrs = <String>[];
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 {
Expand Down Expand Up @@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert(a.name == b.name)

introduced: newIntroduced,
deprecated: newDeprecated,
obsoleted: newObsoleted,
unavailable: a.unavailable || b.unavailable,
);
}

@visibleForTesting
Availability getAvailability(Versions version) {
if (unavailable) {
Expand Down
Loading