Handle ObjC nullable annotations (#624)
* Fix #334
* Fix analysis
* fix swift test
* Fix tests
* Fix nullability bug
* Fix formatting
* Fix linker error
* Fix static methods bug
* fix objective_c_example_test
diff --git a/lib/src/code_generator.dart b/lib/src/code_generator.dart
index 82b2ed6..5977439 100644
--- a/lib/src/code_generator.dart
+++ b/lib/src/code_generator.dart
@@ -19,6 +19,7 @@
export 'code_generator/objc_block.dart';
export 'code_generator/objc_built_in_functions.dart';
export 'code_generator/objc_interface.dart';
+export 'code_generator/objc_nullable.dart';
export 'code_generator/pointer.dart';
export 'code_generator/struct.dart';
export 'code_generator/type.dart';
diff --git a/lib/src/code_generator/objc_built_in_functions.dart b/lib/src/code_generator/objc_built_in_functions.dart
index b092e73..b772861 100644
--- a/lib/src/code_generator/objc_built_in_functions.dart
+++ b/lib/src/code_generator/objc_built_in_functions.dart
@@ -326,7 +326,7 @@
String toString() {
final data = dataUsingEncoding_(
0x94000100 /* NSUTF16LittleEndianStringEncoding */);
- return data.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString(
+ return data!.bytes.cast<${w.ffiPkgLibraryPrefix}.Utf16>().toDartString(
length: length);
}
''');
diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart
index 12ebd14..325692a 100644
--- a/lib/src/code_generator/objc_interface.dart
+++ b/lib/src/code_generator/objc_interface.dart
@@ -76,10 +76,8 @@
if (isStatic) {
stringParams.add('${w.className} _lib');
}
- stringParams.addAll(params.map((p) =>
- (_getConvertedType(p.type, w, name) +
- (p.isNullable ? "? " : " ") +
- p.name)));
+ stringParams.addAll(
+ params.map((p) => '${_getConvertedType(p.type, w, name)} ${p.name}'));
return '(${stringParams.join(", ")})';
}
@@ -146,8 +144,7 @@
s.write(' ');
if (isStatic) {
s.write('static ');
- s.write(
- _getConvertedReturnType(returnType, w, name, m.isNullableReturn));
+ s.write(_getConvertedType(returnType, w, name));
switch (m.kind) {
case ObjCMethodKind.method:
@@ -173,14 +170,12 @@
switch (m.kind) {
case ObjCMethodKind.method:
// returnType methodName(...)
- s.write(_getConvertedReturnType(
- returnType, w, name, m.isNullableReturn));
+ s.write(_getConvertedType(returnType, w, name));
s.write(' $methodName');
s.write(paramsToString(params, isStatic: false));
break;
case ObjCMethodKind.propertyGetter:
- s.write(_getConvertedReturnType(
- returnType, w, name, m.isNullableReturn));
+ s.write(_getConvertedType(returnType, w, name));
if (isStret) {
// void getMethodName(Pointer<returnType> stret, NativeLibrary _lib)
s.write(' get');
@@ -219,8 +214,8 @@
}
s.write(');\n');
if (convertReturn) {
- final result = _doReturnConversion(returnType, '_ret', name, '_lib',
- m.isNullableReturn, m.isOwnedReturn);
+ final result = _doReturnConversion(
+ returnType, '_ret', name, '_lib', m.isOwnedReturn);
s.write(' return $result;');
}
@@ -263,6 +258,7 @@
if (superType != null) {
superType!.addDependencies(dependencies);
_copyMethodsFromSuperType();
+ _fixNullabilityOfOverriddenMethods();
}
for (final m in methods.values) {
@@ -280,12 +276,48 @@
if (m.isClass &&
!_excludedNSObjectClassMethods.contains(m.originalName)) {
addMethod(m);
- } else if (m.returnType is ObjCInstanceType) {
+ } else if (_isInstanceType(m.returnType)) {
addMethod(m);
}
}
}
+ 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 && !superMethod.isClass && !method.isClass) {
+ 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);
+
void addMethod(ObjCMethod method) {
final oldMethod = methods[method.originalName];
if (oldMethod != null) {
@@ -365,39 +397,36 @@
type is ObjCInterface ||
type is ObjCBlock ||
type is ObjCObjectPointer ||
- type is ObjCInstanceType;
+ type is ObjCInstanceType ||
+ type is ObjCNullable;
String _getConvertedType(Type type, Writer w, String enclosingClass) {
if (type is ObjCInstanceType) return enclosingClass;
+ if (type is ObjCNullable && type.child is ObjCInstanceType) {
+ return '$enclosingClass?';
+ }
return type.getDartType(w);
}
- String _getConvertedReturnType(
- Type type, Writer w, String enclosingClass, bool isNullableReturn) {
- final result = _getConvertedType(type, w, enclosingClass);
- if (isNullableReturn) {
- return "$result?";
- }
- return result;
- }
-
String _doArgConversion(ObjCMethodParam arg) {
- if (arg.type is ObjCInterface ||
+ if (arg.type is ObjCNullable) {
+ return '${arg.name}?._id ?? ffi.nullptr';
+ } else if (arg.type is ObjCInterface ||
arg.type is ObjCObjectPointer ||
arg.type is ObjCInstanceType ||
arg.type is ObjCBlock) {
- if (arg.isNullable) {
- return '${arg.name}?._id ?? ffi.nullptr';
- } else {
- return '${arg.name}._id';
- }
+ return '${arg.name}._id';
}
return arg.name;
}
String _doReturnConversion(Type type, String value, String enclosingClass,
- String library, bool isNullable, bool isOwnedReturn) {
- final prefix = isNullable ? '$value.address == 0 ? null : ' : '';
+ String library, bool isOwnedReturn) {
+ var prefix = '';
+ if (type is ObjCNullable) {
+ prefix = '$value.address == 0 ? null : ';
+ type = type.child;
+ }
final ownerFlags = 'retain: ${!isOwnedReturn}, release: true';
if (type is ObjCInterface) {
return '$prefix${type.name}._($value, $library, $ownerFlags)';
@@ -432,8 +461,7 @@
final String? dartDoc;
final String originalName;
final ObjCProperty? property;
- final Type returnType;
- final bool isNullableReturn;
+ Type returnType;
final List<ObjCMethodParam> params;
final ObjCMethodKind kind;
final bool isClass;
@@ -448,7 +476,6 @@
required this.kind,
required this.isClass,
required this.returnType,
- this.isNullableReturn = false,
List<ObjCMethodParam>? params_,
}) : params = params_ ?? [];
@@ -488,7 +515,6 @@
bool sameAs(ObjCMethod other) {
if (originalName != other.originalName) return false;
- if (isNullableReturn != other.isNullableReturn) return false;
if (kind != other.kind) return false;
if (isClass != other.isClass) return false;
// msgSend is deduped by signature, so this check covers the signature.
@@ -501,11 +527,16 @@
originalName.startsWith('new') ||
originalName.startsWith('alloc') ||
originalName.contains(_copyRegExp);
+
+ @override
+ String toString() => '$returnType $originalName(${params.join(', ')})';
}
class ObjCMethodParam {
- final Type type;
+ Type type;
final String name;
- final bool isNullable;
- ObjCMethodParam(this.type, this.name, {this.isNullable = false});
+ ObjCMethodParam(this.type, this.name);
+
+ @override
+ String toString() => '$type $name';
}
diff --git a/lib/src/code_generator/objc_nullable.dart b/lib/src/code_generator/objc_nullable.dart
new file mode 100644
index 0000000..2b08082
--- /dev/null
+++ b/lib/src/code_generator/objc_nullable.dart
@@ -0,0 +1,46 @@
+// 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 'package:ffigen/src/code_generator.dart';
+
+import 'writer.dart';
+
+/// An ObjC type annotated with nullable. Eg:
+/// +(nullable NSObject*) methodWithNullableResult;
+class ObjCNullable extends Type {
+ Type child;
+
+ ObjCNullable(this.child) {
+ assert(isSupported(child));
+ }
+
+ static bool isSupported(Type type) =>
+ type is ObjCInterface ||
+ type is ObjCBlock ||
+ type is ObjCObjectPointer ||
+ type is ObjCInstanceType;
+
+ @override
+ void addDependencies(Set<Binding> dependencies) {
+ child.addDependencies(dependencies);
+ }
+
+ @override
+ Type get baseType => child.baseType;
+
+ @override
+ String getCType(Writer w) => child.getCType(w);
+
+ @override
+ String getFfiDartType(Writer w) => child.getFfiDartType(w);
+
+ @override
+ String getDartType(Writer w) => '${child.getDartType(w)}?';
+
+ @override
+ String toString() => '$child?';
+
+ @override
+ String cacheKey() => '${child.cacheKey()}?';
+}
diff --git a/lib/src/header_parser/clang_bindings/clang_bindings.dart b/lib/src/header_parser/clang_bindings/clang_bindings.dart
index fc5ca17..d5431a9 100644
--- a/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -915,6 +915,23 @@
late final _clang_Type_getAlignOf =
_clang_Type_getAlignOfPtr.asFunction<int Function(CXType)>();
+ /// Return the type that was modified by this attributed type.
+ ///
+ /// If the type is not an attributed type, an invalid type is returned.
+ CXType clang_Type_getModifiedType(
+ CXType T,
+ ) {
+ return _clang_Type_getModifiedType(
+ T,
+ );
+ }
+
+ late final _clang_Type_getModifiedTypePtr =
+ _lookup<ffi.NativeFunction<CXType Function(CXType)>>(
+ 'clang_Type_getModifiedType');
+ late final _clang_Type_getModifiedType =
+ _clang_Type_getModifiedTypePtr.asFunction<CXType Function(CXType)>();
+
/// Determine whether the given cursor represents an anonymous
/// tag or namespace
int clang_Cursor_isAnonymous(
@@ -2623,10 +2640,10 @@
///
/// The visitor should return one of the \c CXChildVisitResult values
/// to direct clang_visitCursorChildren().
-typedef CXCursorVisitor = ffi.Pointer<
- ffi.NativeFunction<
- ffi.Int32 Function(
- CXCursor cursor, CXCursor parent, CXClientData client_data)>>;
+typedef CXCursorVisitor
+ = ffi.Pointer<ffi.NativeFunction<CXCursorVisitor_function>>;
+typedef CXCursorVisitor_function = ffi.Int32 Function(
+ CXCursor cursor, CXCursor parent, CXClientData client_data);
/// Opaque pointer representing client data that will be passed through
/// to various callbacks and visitors.
diff --git a/lib/src/header_parser/parser.dart b/lib/src/header_parser/parser.dart
index 871da44..5be826f 100644
--- a/lib/src/header_parser/parser.dart
+++ b/lib/src/header_parser/parser.dart
@@ -97,7 +97,9 @@
0,
clang_types.CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies |
clang_types.CXTranslationUnit_Flags
- .CXTranslationUnit_DetailedPreprocessingRecord,
+ .CXTranslationUnit_DetailedPreprocessingRecord |
+ clang_types
+ .CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes,
);
if (tu == nullptr) {
diff --git a/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
index 5721905..dc3b786 100644
--- a/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -175,9 +175,6 @@
final isReadOnly = propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly >
0;
- // TODO(#334): Use the nullable attribute to decide this.
- final isNullable =
- cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
final property = ObjCProperty(fieldName);
@@ -193,7 +190,6 @@
kind: ObjCMethodKind.propertyGetter,
isClass: isClass,
returnType: fieldType,
- isNullableReturn: isNullable,
);
itf.addMethod(getter);
@@ -208,8 +204,7 @@
kind: ObjCMethodKind.propertySetter,
isClass: isClass,
returnType: NativeType(SupportedNativeType.Void));
- setter.params
- .add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
+ setter.params.add(ObjCMethodParam(fieldType, 'value'));
itf.addMethod(setter);
}
}
@@ -264,22 +259,7 @@
}
void _parseMethodParam(clang_types.CXCursor cursor) {
- /*
- TODO(#334): Change this to use:
-
- clang.clang_Type_getNullability(cursor.type()) ==
- clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
-
- NOTE: This will only work with the
-
- clang_types
- .CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes
-
- option set.
- */
final parsed = _methodStack.top;
- final isNullable =
- cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
final name = cursor.spelling();
final type = cursor.type().toCodeGenType();
if (type.isIncompleteCompound) {
@@ -291,7 +271,7 @@
}
_logger.fine(
' >> Parameter: $type $name ${cursor.completeStringRepr()}');
- parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
+ parsed.method.params.add(ObjCMethodParam(type, name));
}
void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
diff --git a/lib/src/header_parser/type_extractor/extractor.dart b/lib/src/header_parser/type_extractor/extractor.dart
index c3c8054..311c1e8 100644
--- a/lib/src/header_parser/type_extractor/extractor.dart
+++ b/lib/src/header_parser/type_extractor/extractor.dart
@@ -116,8 +116,8 @@
case clang_types.CXTypeKind.CXType_FunctionNoProto:
// Primarily used for function types with zero arguments.
return _extractFromFunctionProto(cxtype, cursor: originalCursor);
- case clang_types.CXTypeKind
- .CXType_ConstantArray: // Primarily used for constant array in struct members.
+ case clang_types.CXTypeKind.CXType_ConstantArray:
+ // Primarily used for constant array in struct members.
final numElements = clang.clang_getNumElements(cxtype);
final elementType =
clang.clang_getArrayElementType(cxtype).toCodeGenType();
@@ -125,13 +125,25 @@
return numElements == 0
? IncompleteArray(elementType)
: ConstantArray(numElements, elementType);
- case clang_types.CXTypeKind
- .CXType_IncompleteArray: // Primarily used for incomplete array in function parameters.
+ case clang_types.CXTypeKind.CXType_IncompleteArray:
+ // Primarily used for incomplete array in function parameters.
return IncompleteArray(
clang.clang_getArrayElementType(cxtype).toCodeGenType(),
);
case clang_types.CXTypeKind.CXType_Bool:
return BooleanType();
+ case clang_types.CXTypeKind.CXType_Attributed:
+ case clang_types.CXTypeKind.CXType_Unexposed:
+ final innerType = getCodeGenType(
+ clang.clang_Type_getModifiedType(cxtype),
+ ignoreFilter: ignoreFilter,
+ originalCursor: originalCursor,
+ );
+ final isNullable = clang.clang_Type_getNullability(cxtype) ==
+ clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
+ return isNullable && ObjCNullable.isSupported(innerType)
+ ? ObjCNullable(innerType)
+ : innerType;
default:
var typeSpellKey =
clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
diff --git a/test/example_tests/objective_c_example_test.dart b/test/example_tests/objective_c_example_test.dart
index b291edb..eba22e6 100644
--- a/test/example_tests/objective_c_example_test.dart
+++ b/test/example_tests/objective_c_example_test.dart
@@ -31,12 +31,12 @@
expect(
output,
contains(
- 'static NSURL fileURLWithPath_(AVFAudio _lib, NSString? path) {'));
+ 'static NSURL fileURLWithPath_(AVFAudio _lib, NSString path) {'));
expect(output, contains('class AVAudioPlayer extends NSObject {'));
expect(
output,
- contains('AVAudioPlayer initWithContentsOfURL_error_('
- 'NSURL? url, ffi.Pointer<ffi.Pointer<ObjCObject>> outError) {'));
+ contains('AVAudioPlayer? initWithContentsOfURL_error_('
+ 'NSURL url, ffi.Pointer<ffi.Pointer<ObjCObject>> outError) {'));
expect(output, contains('double get duration {'));
expect(output, contains('bool play() {'));
});
diff --git a/test/native_objc_test/nullable_config.yaml b/test/native_objc_test/nullable_config.yaml
index e46cf06..eba9e77 100644
--- a/test/native_objc_test/nullable_config.yaml
+++ b/test/native_objc_test/nullable_config.yaml
@@ -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
diff --git a/test/native_objc_test/nullable_inheritance_config.yaml b/test/native_objc_test/nullable_inheritance_config.yaml
new file mode 100644
index 0000000..930a531
--- /dev/null
+++ b/test/native_objc_test/nullable_inheritance_config.yaml
@@ -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
diff --git a/test/native_objc_test/nullable_inheritance_test.dart b/test/native_objc_test/nullable_inheritance_test.dart
new file mode 100644
index 0000000..fd3052c
--- /dev/null
+++ b/test/native_objc_test/nullable_inheritance_test.dart
@@ -0,0 +1,74 @@
+// 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);
+ });
+ });
+ });
+}
diff --git a/test/native_objc_test/nullable_inheritance_test.m b/test/native_objc_test/nullable_inheritance_test.m
new file mode 100644
index 0000000..035692c
--- /dev/null
+++ b/test/native_objc_test/nullable_inheritance_test.m
@@ -0,0 +1,69 @@
+#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
+@implementation NullableIntermediate
+@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
diff --git a/test/native_objc_test/nullable_test.dart b/test/native_objc_test/nullable_test.dart
index 8814dc5..9aed94e 100644
--- a/test/native_objc_test/nullable_test.dart
+++ b/test/native_objc_test/nullable_test.dart
@@ -15,9 +15,9 @@
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');
@@ -30,12 +30,12 @@
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);
});
});
@@ -46,12 +46,12 @@
test('Null', () {
expect(NullableInterface.returnNil_(lib, true), null);
});
- }, skip: "TODO(#334): enable this test");
+ });
group('Nullable arguments', () {
test('Not null', () {
expect(
- NullableInterface.isNullWithNullableNSObjectArg_(lib, obj!), false);
+ NullableInterface.isNullWithNullableNSObjectArg_(lib, obj), false);
});
test('Null', () {
expect(
@@ -61,8 +61,7 @@
group('Not-nullable arguments', () {
test('Not null', () {
- expect(
- NullableInterface.isNullWithNotNullableNSObjectPtrArg_(lib, obj!),
+ expect(NullableInterface.isNullWithNotNullableNSObjectPtrArg_(lib, obj),
false);
});
});
diff --git a/tool/libclang_config.yaml b/tool/libclang_config.yaml
index b948e5c..f569afd 100644
--- a/tool/libclang_config.yaml
+++ b/tool/libclang_config.yaml
@@ -122,5 +122,6 @@
- clang_Cursor_getObjCPropertyGetterName
- clang_Cursor_getObjCPropertySetterName
- clang_Type_getNullability
+ - clang_Type_getModifiedType
- clang_Location_isInSystemHeader
- clang_getClangVersion