Fix ObjC `instancetype` inheritance issue. (#618)
* Fix #486
* changelog and format
* Fix nit
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b90e619..efc5af3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
- Fix invalid exceptional return value ObjCBlocks that return floats.
- Fix return_of_invalid_type analysis error for ObjCBlocks.
- Fix crash in ObjC methods and blocks that return structs by value.
+- Fix ObjC methods returning instancetype having the wrong type in sublasses.
- Bump min SDK version to 3.2.0-114.0.dev.
# 9.0.1
diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart
index bf4d887..782d75c 100644
--- a/lib/src/code_generator/objc_interface.dart
+++ b/lib/src/code_generator/objc_interface.dart
@@ -263,7 +263,7 @@
if (superType != null) {
superType!.addDependencies(dependencies);
- _copyClassMethodsFromSuperType();
+ _copyMethodsFromSuperType();
}
for (final m in methods.values) {
@@ -271,13 +271,18 @@
}
}
- void _copyClassMethodsFromSuperType() {
- // Copy class methods from the super type, because Dart classes don't
- // inherit static methods.
+ void _copyMethodsFromSuperType() {
+ // We need to copy certain methods from the super type:
+ // - Class methods, because Dart classes don't inherit static methods.
+ // - Methods that return instancetype, because the subclass's copy of the
+ // method needs to return the subclass, not the super class.
+ // Note: instancetype is only allowed as a return type, not an arg type.
for (final m in superType!.methods.values) {
if (m.isClass &&
!_excludedNSObjectClassMethods.contains(m.originalName)) {
addMethod(m);
+ } else if (_isInstanceType(m.returnType)) {
+ addMethod(m);
}
}
}
diff --git a/test/native_objc_test/inherited_instancetype_config.yaml b/test/native_objc_test/inherited_instancetype_config.yaml
new file mode 100644
index 0000000..9261979
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_config.yaml
@@ -0,0 +1,13 @@
+name: InheritedInstancetypeTestObjCLibrary
+description: 'Regression tests for https://github.com/dart-lang/ffigen/issues/486'
+language: objc
+output: 'inherited_instancetype_bindings.dart'
+exclude-all-by-default: true
+objc-interfaces:
+ include:
+ - ChildClass
+headers:
+ entry-points:
+ - 'inherited_instancetype_test.m'
+preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/inherited_instancetype_test.dart b/test/native_objc_test/inherited_instancetype_test.dart
new file mode 100644
index 0000000..93f9355
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, 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')
+
+// Regression tests for https://github.com/dart-lang/ffigen/issues/486.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'inherited_instancetype_bindings.dart';
+import 'util.dart';
+
+void main() {
+ late InheritedInstancetypeTestObjCLibrary lib;
+
+ group('inheritedInstancetype', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib =
+ File('test/native_objc_test/inherited_instancetype_test.dylib');
+ verifySetupFile(dylib);
+ lib = InheritedInstancetypeTestObjCLibrary(
+ DynamicLibrary.open(dylib.absolute.path));
+ generateBindingsForCoverage('inherited_instancetype');
+ });
+
+ test('Ordinary init method', () {
+ final ChildClass child = ChildClass.alloc(lib).init();
+ expect(child.field, 123);
+ final ChildClass sameChild = child.getSelf();
+ sameChild.field = 456;
+ expect(child.field, 456);
+ });
+
+ test('Custom create method', () {
+ final ChildClass child = ChildClass.create(lib);
+ expect(child.field, 123);
+ final ChildClass sameChild = child.getSelf();
+ sameChild.field = 456;
+ expect(child.field, 456);
+ });
+
+ test('Polymorphism', () {
+ final ChildClass child = ChildClass.alloc(lib).init();
+ final BaseClass base = child;
+
+ // Calling base.getSelf() should still go through ChildClass.getSelf, so
+ // the result will have a compile time type of BaseClass, but a runtime
+ // type of ChildClass.
+ final BaseClass sameChild = base.getSelf();
+ expect(sameChild, isA<ChildClass>());
+ });
+ });
+}
diff --git a/test/native_objc_test/inherited_instancetype_test.m b/test/native_objc_test/inherited_instancetype_test.m
new file mode 100644
index 0000000..8f1b20b
--- /dev/null
+++ b/test/native_objc_test/inherited_instancetype_test.m
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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/NSObject.h>
+
+@interface BaseClass : NSObject {}
++ (instancetype)create;
+- (instancetype)getSelf;
+@end
+
+@interface ChildClass : BaseClass {}
+@property int32_t field;
+@end
+
+@implementation BaseClass
++ (instancetype)create {
+ return [[[self class] alloc] init];
+}
+
+- (instancetype)getSelf {
+ return self;
+}
+@end
+
+@implementation ChildClass
+- (instancetype)init {
+ if (self = [super init]) {
+ self.field = 123;
+ }
+ return self;
+}
+@end