Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 4 additions & 1 deletion pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ObjCInterface extends BindingType with ObjCMethods, HasLocalScope {
final categories = <ObjCCategory>[];
final subtypes = <ObjCInterface>[];
final ApiAvailability apiAvailability;
final Set<String> swiftUnavailableSelectors = {};

// Filled by ListBindingsVisitation.
bool generateAsStub = false;
Expand Down Expand Up @@ -168,7 +169,9 @@ ${generateInstanceMethodBindings(w, this)}
m.originalName == 'new',
)
.firstOrNull;
if (newMethod != null && originalName != 'NSString') {
if (newMethod != null &&
originalName != 'NSString' &&
!swiftUnavailableSelectors.contains('new')) {
s.write('''
/// Returns a new instance of $name constructed with the default `new` method.
$name() : this.as(${newMethod.name}().object\$);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ApiAvailability {
final bool alwaysUnavailable;
final PlatformAvailability? ios;
final PlatformAvailability? macos;
final bool swiftUnavailable;

late final Availability availability;

Expand All @@ -27,6 +28,7 @@ class ApiAvailability {
this.alwaysUnavailable = false,
this.ios,
this.macos,
this.swiftUnavailable = false,
required ExternalVersions? externalVersions,
}) {
availability = _getAvailability(externalVersions);
Expand Down Expand Up @@ -64,6 +66,7 @@ class ApiAvailability {

PlatformAvailability? ios;
PlatformAvailability? macos;
var swiftIsUnavailable = false;

for (var i = 0; i < platformsLength; ++i) {
final platform = platforms[i];
Expand All @@ -80,6 +83,9 @@ class ApiAvailability {
case 'macos':
macos = platformAvailability..name = 'macOS';
break;
case 'swift':
if (platformAvailability.unavailable) swiftIsUnavailable = true;
break;
}
}

Expand All @@ -88,6 +94,7 @@ class ApiAvailability {
alwaysUnavailable: alwaysUnavailable.value != 0,
ios: ios,
macos: macos,
swiftUnavailable: swiftIsUnavailable,
externalVersions: context.config.objectiveC?.externalVersions,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ void fillObjCInterfaceMethodsIfNeeded(
'Name: ${itf.originalName}, ${cursor.completeStringRepr()}',
);

if (!cursor.isInSystemHeader()) {
cursor.visitChildren((child) {
final isMethodDecl =
child.kind ==
clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl ||
child.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
if (isMethodDecl) {
final avail = ApiAvailability.fromCursor(child, context);
if (avail.swiftUnavailable) {
itf.swiftUnavailableSelectors.add(child.spelling());
context.logger.info(
'Marking swift-unavailable: '
'${itf.originalName}.${child.spelling()}',
);
}
}
});
}

final itfDecl = Declaration(usr: itf.usr, originalName: itf.originalName);
cursor.visitChildren((child) {
switch (child.kind) {
Expand Down
83 changes: 83 additions & 0 deletions pkgs/ffigen/test/native_objc_test/swift_unavailable_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// Objective C support is only available on mac.
@TestOn('mac-os')
library;

import 'dart:io';

import 'package:ffigen/ffigen.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

import '../test_utils.dart';

void main() {
group('swift_unavailable', () {
late final String bindings;

setUpAll(() {
FfiGenerator(
output: Output(
dartFile: Uri.file(
path.join(
packagePathForTests,
'test',
'native_objc_test',
'swift_unavailable_bindings.dart',
),
),
format: false,
style: const DynamicLibraryBindings(
wrapperName: 'SwiftUnavailableTestLibrary',
wrapperDocComment: 'Tests SWIFT_UNAVAILABLE annotation',
),
),
headers: Headers(
entryPoints: [
Uri.file(
path.join(
packagePathForTests,
'test',
'native_objc_test',
'swift_unavailable_test.m',
),
),
],
),
objectiveC: ObjectiveC(
interfaces: Interfaces(
include: (decl) => {'Animal'}.contains(decl.originalName),
),
),
).generate(logger: createTestLogger());

bindings = File(
path.join(
packagePathForTests,
'test',
'native_objc_test',
'swift_unavailable_bindings.dart',
),
).readAsStringSync();
});

test('initWithName is generated (designated initializer)', () {
expect(bindings, contains('initWithName'));
});

test('init is NOT generated (SWIFT_UNAVAILABLE)', () {
expect(RegExp(r"'init'\b").hasMatch(bindings), isFalse);
});

test('new is NOT generated (SWIFT_UNAVAILABLE_MSG)', () {
expect(bindings, isNot(contains("'new'")));
});

test('no-arg Animal() constructor is NOT generated', () {
expect(bindings, isNot(contains('Animal()')));
});
});
}
20 changes: 20 additions & 0 deletions pkgs/ffigen/test/native_objc_test/swift_unavailable_test.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#import <Foundation/Foundation.h>

#define SWIFT_UNAVAILABLE \
__attribute__((availability(swift, unavailable)))
#define SWIFT_UNAVAILABLE_MSG(msg) \
__attribute__((availability(swift, unavailable, message = msg)))
#define OBJC_DESIGNATED_INITIALIZER \
__attribute__((objc_designated_initializer))

@interface Animal : NSObject
@property(nonatomic, copy) NSString* _Nonnull name;
- (nonnull instancetype)initWithName:(NSString* _Nonnull)name
OBJC_DESIGNATED_INITIALIZER;
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
@end
Loading