From 6ca8e688ae8fab7624a511a33c2d50b4144cf29e Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 28 Sep 2023 13:12:44 -0700 Subject: [PATCH] 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 --- lib/src/code_generator.dart | 1 + .../objc_built_in_functions.dart | 2 +- lib/src/code_generator/objc_interface.dart | 107 +++++++++++------- lib/src/code_generator/objc_nullable.dart | 46 ++++++++ .../clang_bindings/clang_bindings.dart | 25 +++- lib/src/header_parser/parser.dart | 4 +- .../sub_parsers/objcinterfacedecl_parser.dart | 24 +--- .../type_extractor/extractor.dart | 20 +++- .../objective_c_example_test.dart | 6 +- test/native_objc_test/nullable_config.yaml | 2 +- .../nullable_inheritance_config.yaml | 14 +++ .../nullable_inheritance_test.dart | 74 ++++++++++++ .../nullable_inheritance_test.m | 69 +++++++++++ test/native_objc_test/nullable_test.dart | 21 ++-- tool/libclang_config.yaml | 1 + 15 files changed, 331 insertions(+), 85 deletions(-) create mode 100644 lib/src/code_generator/objc_nullable.dart create mode 100644 test/native_objc_test/nullable_inheritance_config.yaml create mode 100644 test/native_objc_test/nullable_inheritance_test.dart create mode 100644 test/native_objc_test/nullable_inheritance_test.m diff --git a/lib/src/code_generator.dart b/lib/src/code_generator.dart index 82b2ed6b..5977439a 100644 --- a/lib/src/code_generator.dart +++ b/lib/src/code_generator.dart @@ -19,6 +19,7 @@ export 'code_generator/native_type.dart'; 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 b092e738..b772861e 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 @@ class $name implements ${w.ffiLibraryPrefix}.Finalizable { 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 12ebd14e..325692ab 100644 --- a/lib/src/code_generator/objc_interface.dart +++ b/lib/src/code_generator/objc_interface.dart @@ -76,10 +76,8 @@ class ObjCInterface extends BindingType { 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 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { 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 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { 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 stret, NativeLibrary _lib) s.write(' get'); @@ -219,8 +214,8 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { } 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 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { if (superType != null) { superType!.addDependencies(dependencies); _copyMethodsFromSuperType(); + _fixNullabilityOfOverriddenMethods(); } for (final m in methods.values) { @@ -280,12 +276,48 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { 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 @@ class $name extends ${superType?.name ?? '_ObjCWrapper'} { 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; - return type.getDartType(w); - } - - String _getConvertedReturnType( - Type type, Writer w, String enclosingClass, bool isNullableReturn) { - final result = _getConvertedType(type, w, enclosingClass); - if (isNullableReturn) { - return "$result?"; + if (type is ObjCNullable && type.child is ObjCInstanceType) { + return '$enclosingClass?'; } - return result; + return type.getDartType(w); } 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 @@ class ObjCMethod { final String? dartDoc; final String originalName; final ObjCProperty? property; - final Type returnType; - final bool isNullableReturn; + Type returnType; final List params; final ObjCMethodKind kind; final bool isClass; @@ -448,7 +476,6 @@ class ObjCMethod { required this.kind, required this.isClass, required this.returnType, - this.isNullableReturn = false, List? params_, }) : params = params_ ?? []; @@ -488,7 +515,6 @@ class ObjCMethod { 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 @@ class ObjCMethod { 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 00000000..2b080827 --- /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 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 fc5ca177..d5431a95 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 @@ class Clang { late final _clang_Type_getAlignOf = _clang_Type_getAlignOfPtr.asFunction(); + /// 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>( + 'clang_Type_getModifiedType'); + late final _clang_Type_getModifiedType = + _clang_Type_getModifiedTypePtr.asFunction(); + /// Determine whether the given cursor represents an anonymous /// tag or namespace int clang_Cursor_isAnonymous( @@ -2623,10 +2640,10 @@ abstract class CXChildVisitResult { /// /// 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>; +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 871da44e..5be826fd 100644 --- a/lib/src/header_parser/parser.dart +++ b/lib/src/header_parser/parser.dart @@ -97,7 +97,9 @@ List parseToBindings() { 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 57219050..dc3b7865 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 @@ void _parseProperty(clang_types.CXCursor cursor) { 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 @@ void _parseProperty(clang_types.CXCursor cursor) { kind: ObjCMethodKind.propertyGetter, isClass: isClass, returnType: fieldType, - isNullableReturn: isNullable, ); itf.addMethod(getter); @@ -208,8 +204,7 @@ void _parseProperty(clang_types.CXCursor cursor) { 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 @@ int _parseMethodVisitor(clang_types.CXCursor cursor, } 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 @@ void _parseMethodParam(clang_types.CXCursor cursor) { } _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 c3c80541..311c1e8b 100644 --- a/lib/src/header_parser/type_extractor/extractor.dart +++ b/lib/src/header_parser/type_extractor/extractor.dart @@ -116,8 +116,8 @@ Type getCodeGenType( 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 @@ Type getCodeGenType( 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 b291edbc..eba22e61 100644 --- a/test/example_tests/objective_c_example_test.dart +++ b/test/example_tests/objective_c_example_test.dart @@ -31,12 +31,12 @@ void main() { 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> outError) {')); + contains('AVAudioPlayer? initWithContentsOfURL_error_(' + 'NSURL url, ffi.Pointer> 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 e46cf060..eba9e770 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 00000000..930a5312 --- /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 00000000..fd3052ce --- /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()); + expect(nullableBase.nullableReturn_(true), null); + }); + + test('Non-null return', () { + expect(nullableBase.nonNullReturn(), isA()); + }); + }); + + 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()); + }); + + 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 00000000..035692cd --- /dev/null +++ b/test/native_objc_test/nullable_inheritance_test.m @@ -0,0 +1,69 @@ +#import + +@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 8814dc5e..9aed94e1 100644 --- a/test/native_objc_test/nullable_test.dart +++ b/test/native_objc_test/nullable_test.dart @@ -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'); @@ -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); }); }); @@ -46,12 +46,12 @@ void main() { 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 @@ void main() { 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 b948e5ce..f569afd1 100644 --- a/tool/libclang_config.yaml +++ b/tool/libclang_config.yaml @@ -122,5 +122,6 @@ functions: - clang_Cursor_getObjCPropertyGetterName - clang_Cursor_getObjCPropertySetterName - clang_Type_getNullability + - clang_Type_getModifiedType - clang_Location_isInSystemHeader - clang_getClangVersion