Skip to content
This repository has been archived by the owner on Jan 28, 2024. It is now read-only.

Fix ObjC instancetype inheritance issue. #618

Merged
merged 3 commits into from
Sep 12, 2023
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,21 +263,26 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {

if (superType != null) {
superType!.addDependencies(dependencies);
_copyClassMethodsFromSuperType();
_copyMethodsFromSuperType();
}

for (final m in methods.values) {
m.addDependencies(dependencies, builtInFunctions);
}
}

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);
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions test/native_objc_test/inherited_instancetype_config.yaml
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions test/native_objc_test/inherited_instancetype_test.dart
Original file line number Diff line number Diff line change
@@ -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>());
});
});
}
33 changes: 33 additions & 0 deletions test/native_objc_test/inherited_instancetype_test.m
Original file line number Diff line number Diff line change
@@ -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
Loading