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 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
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
liamappelbe marked this conversation as resolved.
Show resolved Hide resolved

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