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

Commit

Permalink
Fix nullability bug
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe committed Sep 28, 2023
1 parent 29cad88 commit ad26be6
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 13 deletions.
34 changes: 32 additions & 2 deletions lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
if (superType != null) {
superType!.addDependencies(dependencies);
_copyMethodsFromSuperType();
_fixNullabilityOfOverriddenMethods();
}

for (final m in methods.values) {
Expand All @@ -281,6 +282,35 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} {
}
}

void _fixNullabilityOfOverriddenMethods() {
// ObjC ignores nullability when deciding if an override for an inherited
// method is valid. But in Dart it's invalid to override a method and change
// it's return type from non-null to nullable, or its arg type from nullable
// to non-null. So in these cases we have to make the non-null type
// nullable, to avoid Dart compile errors.
var superType_ = superType;
while (superType_ != null) {
for (final method in methods.values) {
final superMethod = superType_.methods[method.originalName];
if (superMethod != null) {
if (superMethod.returnType.typealiasType is! ObjCNullable && method.returnType.typealiasType is ObjCNullable) {
superMethod.returnType = ObjCNullable(superMethod.returnType);
}
final numArgs = method.params.length < superMethod.params.length ?
method.params.length : superMethod.params.length;
for (int i = 0; i < numArgs; ++i) {
final param = method.params[i];
final superParam = superMethod.params[i];
if (superParam.type.typealiasType is ObjCNullable && param.type.typealiasType is! ObjCNullable) {
param.type = ObjCNullable(param.type);
}
}
}
}
superType_ = superType_.superType;
}
}

static bool _isInstanceType(Type type) =>
type is ObjCInstanceType ||
(type is ObjCNullable && type.child is ObjCInstanceType);
Expand Down Expand Up @@ -428,7 +458,7 @@ class ObjCMethod {
final String? dartDoc;
final String originalName;
final ObjCProperty? property;
final Type returnType;
Type returnType;
final List<ObjCMethodParam> params;
final ObjCMethodKind kind;
final bool isClass;
Expand Down Expand Up @@ -497,7 +527,7 @@ class ObjCMethod {
}

class ObjCMethodParam {
final Type type;
Type type;
final String name;
ObjCMethodParam(this.type, this.name);
}
3 changes: 2 additions & 1 deletion lib/src/code_generator/objc_nullable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import 'package:ffigen/src/code_generator.dart';

import 'writer.dart';

/// An ObjC type annotated with nullable.
/// An ObjC type annotated with nullable. Eg:
/// +(nullable NSObject*) methodWithNullableResult;
class ObjCNullable extends Type {
Type child;

Expand Down
2 changes: 1 addition & 1 deletion test/native_objc_test/nullable_config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: NullableTestObjCLibrary
description: 'Tests calling Objective-C methods'
description: 'Tests nullability for Objective-C methods'
language: objc
output: 'nullable_bindings.dart'
exclude-all-by-default: true
Expand Down
14 changes: 14 additions & 0 deletions test/native_objc_test/nullable_inheritance_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: NullableInheritanceTestObjCLibrary
description: 'Tests nullability of inherited Objective-C methods'
language: objc
output: 'nullable_inheritance_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- NullableBase
- NullableChild
headers:
entry-points:
- 'nullable_inheritance_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
73 changes: 73 additions & 0 deletions test/native_objc_test/nullable_inheritance_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2022, 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')

import 'dart:ffi';
import 'dart:io';

import 'package:test/test.dart';
import '../test_utils.dart';
import 'nullable_inheritance_bindings.dart';
import 'util.dart';

void main() {
late NullableInheritanceTestObjCLibrary lib;
late NullableBase nullableBase;
late NullableChild nullableChild;
late NSObject obj;
group('Nullable inheritance', () {
setUpAll(() {
logWarnings();
final dylib = File('test/native_objc_test/nullable_inheritance_test.dylib');
verifySetupFile(dylib);
lib = NullableInheritanceTestObjCLibrary(
DynamicLibrary.open(dylib.absolute.path));
nullableBase = NullableBase.new1(lib);
nullableChild = NullableChild.new1(lib);
obj = NSObject.new1(lib);
generateBindingsForCoverage('nullable');
});

group('Base', () {
test('Nullable arguments', () {
expect(nullableBase.nullableArg_(obj), false);
expect(nullableBase.nullableArg_(null), true);
});

test('Non-null arguments', () {
expect(nullableBase.nonNullArg_(obj), false);
});

test('Nullable return', () {
expect(nullableBase.nullableReturn_(false), isA<NSObject>());
expect(nullableBase.nullableReturn_(true), null);
});

test('Non-null return', () {
expect(nullableBase.nonNullReturn(), isA<NSObject>());
});
});

group('Child', () {
test('Nullable arguments, changed to non-null', () {
expect(nullableChild.nullableArg_(obj), false);
});

test('Non-null arguments, changed to nullable', () {
expect(nullableChild.nonNullArg_(obj), false);
expect(nullableChild.nonNullArg_(null), true);
});

test('Nullable return, changed to non-null', () {
expect(nullableChild.nullableReturn_(false), isA<NSObject>());
});

test('Non-null return, changed to nullable', () {
expect(nullableChild.nonNullReturn(), null);
});
});
});
}
67 changes: 67 additions & 0 deletions test/native_objc_test/nullable_inheritance_test.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#import <Foundation/NSObject.h>

@interface NullableBase : NSObject {}

-(BOOL) nullableArg:(nullable NSObject *)x;
-(BOOL) nonNullArg:(NSObject *)x;
-(nullable NSObject *) nullableReturn:(BOOL)r;
-(NSObject*) nonNullReturn;

@end

@implementation NullableBase

-(BOOL) nullableArg:(nullable NSObject *)x {
return x == NULL;
}

-(BOOL) nonNullArg:(NSObject *)x {
return x == NULL;
}

-(nullable NSObject *) nullableReturn:(BOOL)r {
if (r) {
return nil;
} else {
return [NSObject new];
}
}

-(NSObject *) nonNullReturn {
return [NSObject new];
}

@end

@interface NullableIntermediate : NullableBase {}
@end

@interface NullableChild : NullableIntermediate {}

// Redeclare the same methods with different nullability.
-(BOOL) nullableArg:(NSObject *)x;
-(BOOL) nonNullArg:(nullable NSObject *)x;
-(NSObject *) nullableReturn:(BOOL)r;
-(nullable NSObject *) nonNullReturn;

@end

@implementation NullableChild

-(BOOL) nullableArg:(NSObject *)x {
return x == NULL;
}

-(BOOL) nonNullArg:(nullable NSObject *)x {
return x == NULL;
}

-(NSObject *) nullableReturn:(BOOL)r {
return [NSObject new];
}

-(nullable NSObject *) nonNullReturn {
return nil;
}

@end
18 changes: 9 additions & 9 deletions test/native_objc_test/nullable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import 'util.dart';

void main() {
late NullableTestObjCLibrary lib;
NullableInterface? nullableInterface;
NSObject? obj;
group('method calls', () {
late NullableInterface nullableInterface;
late NSObject obj;
group('Nullability', () {
setUpAll(() {
logWarnings();
final dylib = File('test/native_objc_test/nullable_test.dylib');
Expand All @@ -30,12 +30,12 @@ void main() {

group('Nullable property', () {
test('Not null', () {
nullableInterface!.nullableObjectProperty = obj!;
expect(nullableInterface!.nullableObjectProperty, obj!);
nullableInterface.nullableObjectProperty = obj;
expect(nullableInterface.nullableObjectProperty, obj);
});
test('Null', () {
nullableInterface!.nullableObjectProperty = null;
expect(nullableInterface!.nullableObjectProperty, null);
nullableInterface.nullableObjectProperty = null;
expect(nullableInterface.nullableObjectProperty, null);
});
});

Expand All @@ -51,7 +51,7 @@ void main() {
group('Nullable arguments', () {
test('Not null', () {
expect(
NullableInterface.isNullWithNullableNSObjectArg_(lib, obj!), false);
NullableInterface.isNullWithNullableNSObjectArg_(lib, obj), false);
});
test('Null', () {
expect(
Expand All @@ -62,7 +62,7 @@ void main() {
group('Not-nullable arguments', () {
test('Not null', () {
expect(
NullableInterface.isNullWithNotNullableNSObjectPtrArg_(lib, obj!),
NullableInterface.isNullWithNotNullableNSObjectPtrArg_(lib, obj),
false);
});
});
Expand Down

0 comments on commit ad26be6

Please sign in to comment.