diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 77aa320421..4703c7ee77 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -25,6 +25,8 @@ generating them as stubs. `include-transitive-objc-protocols` defaults to false, and `include-transitive-objc-categories` defaults to true, but these both replicate the existing behavior. +- Fix [bugs](https://github.com/dart-lang/native/issues/1220) caused by + mismatches between ObjC and Dart's inheritance rules. ## 15.0.0 diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart index 479242c2b6..eeedbd136e 100644 --- a/pkgs/ffigen/lib/src/code_generator/func.dart +++ b/pkgs/ffigen/lib/src/code_generator/func.dart @@ -234,6 +234,7 @@ class Parameter extends AstNode { String name; Type type; final bool objCConsumed; + bool isCovariant = false; Parameter({ String? originalName, diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index d3a3c2bd0f..f0c6d9b9e6 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart @@ -119,6 +119,21 @@ class FunctionType extends Type { visitor.visitAll(parameters); visitor.visitAll(varArgParameters); } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is FunctionType) { + return Type.isSupertypeOfVariance( + covariantLeft: [returnType], + covariantRight: [other.returnType], + contravariantLeft: dartTypeParameters.map((p) => p.type).toList(), + contravariantRight: + other.dartTypeParameters.map((p) => p.type).toList(), + ); + } + return false; + } } /// Represents a NativeFunction. @@ -165,4 +180,11 @@ class NativeFunc extends Type { super.visitChildren(visitor); visitor.visit(_type); } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is NativeFunc) return type.isSupertypeOf(other.type); + return false; + } } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 540584ebcd..f466c623d4 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -389,4 +389,18 @@ $blockName $fnName($blockName block) NS_RETURNS_RETAINED { visitor.visitAll(params); visitor.visit(_wrapListenerBlock); } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is ObjCBlock) { + return Type.isSupertypeOfVariance( + covariantLeft: [returnType], + covariantRight: [other.returnType], + contravariantLeft: params.map((p) => p.type).toList(), + contravariantRight: other.params.map((p) => p.type).toList(), + ); + } + return false; + } } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart index ee85890a4c..6d9e03ed2f 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart @@ -200,4 +200,15 @@ ${generateAsStub ? '' : _generateMethods(w)} // inclusion. Including an interface shouldn't auto-include all its // subtypes, even as stubs. } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is ObjCInterface) { + for (ObjCInterface? t = other; t != null; t = t.superType) { + if (t == this) return true; + } + } + return false; + } } diff --git a/pkgs/ffigen/lib/src/code_generator/objc_methods.dart b/pkgs/ffigen/lib/src/code_generator/objc_methods.dart index c8ad658eef..59054dce54 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_methods.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_methods.dart @@ -333,7 +333,9 @@ class ObjCMethod extends AstNode { final targetType = target.getDartType(w); final returnTypeStr = _getConvertedReturnType(w, targetType); final paramStr = [ - for (final p in params) '${p.type.getDartType(w)} ${p.name}', + for (final p in params) + '${p.isCovariant ? 'covariant ' : ''}' + '${p.type.getDartType(w)} ${p.name}', ].join(', '); // The method declaration. diff --git a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart index 4d508fa564..e8bbb5309b 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart @@ -98,4 +98,17 @@ class ObjCNullable extends Type { super.visitChildren(visitor); visitor.visit(child); } + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + + if (other is ObjCNullable) { + // T? :> S? if T :> S + return child.isSupertypeOf(other.child); + } else { + // T? :> S if T :> S + return child.isSupertypeOf(other); + } + } } diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index 320f313650..b4a21c01e9 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -51,6 +51,15 @@ class PointerType extends Type { @override void visit(Visitation visitation) => visitation.visitPointerType(this); + + @override + bool isSupertypeOf(Type other) { + other = other.typealiasType; + if (other is PointerType) { + return child.isSupertypeOf(other.child); + } + return false; + } } /// Represents a constant array, which has a fixed size. diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index ae68cc61e5..552945c942 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart @@ -33,6 +33,18 @@ abstract class Type extends AstNode { /// Returns true if the type is a [Compound] and is incomplete. bool get isIncompleteCompound => false; + /// Returns true if this is a subtype of [other]. That is this <: other. + /// + /// The behavior of this function should mirror Dart's subtyping logic, not + /// Objective-C's. It's used to detect and fix cases where the generated + /// bindings would fail `dart analyze` due to Dart's subtyping rules. + /// + /// Note: Implementers should implement [isSupertypeOf]. + bool isSubtypeOf(Type other) => other.isSupertypeOf(this); + + /// Returns true if this is a supertype of [other]. That is this :> other. + bool isSupertypeOf(Type other) => typealiasType == other.typealiasType; + /// Returns the C type of the Type. This is the FFI compatible type that is /// passed to native code. String getCType(Writer w) => @@ -124,6 +136,28 @@ abstract class Type extends AstNode { @override void visit(Visitation visitation) => visitation.visitType(this); + + // Helper for [isSupertypeOf] that applies variance rules. + static bool isSupertypeOfVariance({ + List covariantLeft = const [], + List covariantRight = const [], + List contravariantLeft = const [], + List contravariantRight = const [], + }) => + isSupertypeOfCovariance(left: covariantLeft, right: covariantRight) && + isSupertypeOfCovariance( + left: contravariantRight, right: contravariantLeft); + + static bool isSupertypeOfCovariance({ + required List left, + required List right, + }) { + if (left.length != right.length) return false; + for (var i = 0; i < left.length; ++i) { + if (!left[i].isSupertypeOf(right[i])) return false; + } + return true; + } } /// Base class for all Type bindings. @@ -152,6 +186,12 @@ abstract class BindingType extends NoLookUpBinding implements Type { @override bool get isIncompleteCompound => false; + @override + bool isSubtypeOf(Type other) => other.isSupertypeOf(this); + + @override + bool isSupertypeOf(Type other) => typealiasType == other.typealiasType; + @override String getFfiDartType(Writer w) => getCType(w); diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart index 6dee9875dc..739e00d47f 100644 --- a/pkgs/ffigen/lib/src/code_generator/typealias.dart +++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart @@ -209,6 +209,9 @@ class Typealias extends BindingType { @override void visit(Visitation visitation) => visitation.visitTypealias(this); + + @override + bool isSupertypeOf(Type other) => type.isSupertypeOf(other); } /// Objective C's instancetype. diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart index d9ab7df2fb..dde0a22c88 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart @@ -61,6 +61,12 @@ ObjCProtocol? parseObjCProtocolDeclaration(clang_types.CXCursor cursor) { protocol.superProtocols.add(superProtocol); } break; + case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl: + final (getter, setter) = + parseObjCProperty(child, decl, config.objcProtocols); + protocol.addMethod(getter); + protocol.addMethod(setter); + break; case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl: case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl: protocol.addMethod(parseObjCMethod(child, decl, config.objcProtocols)); diff --git a/pkgs/ffigen/lib/src/visitor/copy_methods_from_super_type.dart b/pkgs/ffigen/lib/src/visitor/copy_methods_from_super_type.dart index 65431f6f55..91a2ce11e2 100644 --- a/pkgs/ffigen/lib/src/visitor/copy_methods_from_super_type.dart +++ b/pkgs/ffigen/lib/src/visitor/copy_methods_from_super_type.dart @@ -87,13 +87,7 @@ class CopyMethodsFromSuperTypesVisitation extends Visitation { for (final proto in protocols) { for (final m in proto.methods) { if (isNSObject) { - if (m.originalName == 'description' || m.originalName == 'hash') { - // TODO(https://github.com/dart-lang/native/issues/1220): Remove - // this special case. These methods only clash because they're - // sometimes declared as getters and sometimes as normal methods. - } else { - addMethod(m); - } + addMethod(m); } else if (!_excludedNSObjectMethods.contains(m.originalName)) { addMethod(m); } diff --git a/pkgs/ffigen/lib/src/visitor/fix_overridden_methods.dart b/pkgs/ffigen/lib/src/visitor/fix_overridden_methods.dart index aa6e52a3b5..8e989559da 100644 --- a/pkgs/ffigen/lib/src/visitor/fix_overridden_methods.dart +++ b/pkgs/ffigen/lib/src/visitor/fix_overridden_methods.dart @@ -2,46 +2,118 @@ // 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:logging/logging.dart'; + import '../code_generator.dart'; import 'ast.dart'; +final _logger = Logger('ffigen.visitor.FixOverriddenMethodsVisitation'); + class FixOverriddenMethodsVisitation extends Visitation { @override void visitObjCInterface(ObjCInterface node) { - node.visitChildren(visitor); - _fixNullability(node); + // Visit the supertype, then perform all AST mutations, then visit the other + // children. That way we can be sure that the supertype's AST mutations have + // been completed before this node's mutations (this is important for + // _fixContravariantReturns). + visitor.visit(node.superType); + + _fixMethodVariance(node); _fixMethodsVsProperties(node); + + node.visitChildren(visitor); } - void _fixNullability(ObjCInterface node) { - // 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. + (ObjCInterface?, ObjCMethod?) _findNearestWithMethod( + ObjCInterface node, ObjCMethod method) { for (var t = node.superType; t != null; t = t.superType) { - for (final method in node.methods) { - final superMethod = t.getSimilarMethod(method); - if (superMethod != null && - !superMethod.isClassMethod && - !method.isClassMethod) { - 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 (var 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); - } - } - } + final tMethod = t.getSimilarMethod(method); + if (tMethod != null) { + return (t, tMethod); + } + } + return (null, null); + } + + void _fixContravariantReturns(ObjCInterface node, ObjCMethod method, + ObjCInterface superType, ObjCMethod superMethod) { + // In Dart, method return types are covariant, but ObjC allows them to be + // contravariant. So fix these cases by changing the supertype's return type + // to match the subtype's return type. + + if (method.returnType.isSubtypeOf(superMethod.returnType)) { + // Covariant return, nothing to fix. + return; + } + + if (!superMethod.returnType.isSubtypeOf(method.returnType)) { + // Types are unrelated, so this can't be sensibly fixed. + _logger.severe( + '${node.originalName} is a subtype of ${superType.originalName} but ' + 'the return types of their ${method.originalName} methods are ' + 'unrelated'); + return; + } + + superMethod.returnType = method.returnType; + _logger.info('Changed the return type of ' + '${superType.originalName}.${superMethod.originalName} to ' + '${method.returnType} to match ${node.originalName}'); + + final (superSuperType, superSuperMethod) = + _findNearestWithMethod(superType, superMethod); + if (superSuperType != null && superSuperMethod != null) { + _fixContravariantReturns(node, method, superSuperType, superSuperMethod); + } + } + + void _fixCoavariantArgs(ObjCInterface node, ObjCMethod method, + ObjCInterface superType, ObjCMethod superMethod) { + // In Dart, method arg types are contravariant, but ObjC allows them to be + // covariant. So fix these cases by adding the `covariant` keyword to the + // parameter. + final n = method.params.length; + if (n != superMethod.params.length) { + _logger.severe( + '${node.originalName} is a subtype of ${superType.originalName} but ' + 'their ${method.originalName} methods have a different number of ' + 'parameters'); + return; + } + + for (var i = 0; i < n; ++i) { + final pt = method.params[i].type; + final st = superMethod.params[i].type; + + if (st.isSubtypeOf(pt)) { + // Contravariant param, nothing to fix. + continue; + } + + if (!pt.isSubtypeOf(st)) { + // Types are unrelated, so this can't be sensibly fixed. + _logger.severe( + '${node.originalName} is a subtype of ${superType.originalName} ' + 'but their ${method.originalName} methods have a parameter at ' + 'position ${i + 1} with an unrelated type'); + return; + } + + _logger.info('Set the parameter of ' + '${node.originalName}.${method.originalName} at position ${i + 1} to ' + 'be covariant'); + method.params[i].isCovariant = true; + } + } + + void _fixMethodVariance(ObjCInterface node) { + for (final method in node.methods) { + if (method.isClassMethod) continue; + final (superType, superMethod) = _findNearestWithMethod(node, method); + if (superType != null && superMethod != null) { + _fixContravariantReturns(node, method, superType, superMethod); + _fixCoavariantArgs(node, method, superType, superMethod); } } } @@ -54,6 +126,7 @@ class FixOverriddenMethodsVisitation extends Visitation { // up to find the root of the subtree that has this method, then we walk // down the subtree to change all conflicting methods to properties. for (final method in node.methods) { + if (method.isClassMethod) continue; final (root, rootMethod) = _findRootWithMethod(node, method); if (method.isProperty == rootMethod.isProperty) continue; _convertAllSubtreeMethodsToProperties(root, rootMethod); @@ -79,6 +152,8 @@ class FixOverriddenMethodsVisitation extends Visitation { final method = node.getSimilarMethod(rootMethod); if (method != null && method.kind == ObjCMethodKind.method) { method.kind = ObjCMethodKind.propertyGetter; + _logger.info( + 'Converted ${node.originalName}.${method.originalName} to a getter'); } for (final t in node.subtypes) { _convertAllSubtreeMethodsToProperties(t, rootMethod); diff --git a/pkgs/ffigen/test/large_integration_tests/large_objc_test.dart b/pkgs/ffigen/test/large_integration_tests/large_objc_test.dart index 06ba756458..5d003f351d 100644 --- a/pkgs/ffigen/test/large_integration_tests/large_objc_test.dart +++ b/pkgs/ffigen/test/large_integration_tests/large_objc_test.dart @@ -39,28 +39,6 @@ void main() { shouldIncludeMember: randInclude, ); - // TODO(https://github.com/dart-lang/native/issues/1220): Allow these. - const disallowedMethods = { - 'accessKey', - 'allowsCellularAccess', - 'allowsConstrainedNetworkAccess', - 'attributedString', - 'cachePolicy', - 'candidateListTouchBarItem', - 'delegate', - 'hyphenationFactor', - 'image', - 'isProxy', - 'objCType', - 'tag', - 'title', - }; - final interfaceFilter = DeclarationFilters( - shouldInclude: randInclude, - shouldIncludeMember: (_, method) => - randInclude() && !disallowedMethods.contains(method), - ); - const outFile = 'test/large_integration_tests/large_objc_bindings.dart'; const outObjCFile = 'test/large_integration_tests/large_objc_bindings.m'; final config = Config( @@ -80,7 +58,7 @@ void main() { unnamedEnumConstants: randomFilter, globals: randomFilter, typedefs: randomFilter, - objcInterfaces: interfaceFilter, + objcInterfaces: randomFilter, objcProtocols: randomFilter, objcCategories: randomFilter, externalVersions: ExternalVersions( diff --git a/pkgs/ffigen/test/native_objc_test/bad_override_config.yaml b/pkgs/ffigen/test/native_objc_test/bad_override_config.yaml index 83939c85ec..c605b7fd9b 100644 --- a/pkgs/ffigen/test/native_objc_test/bad_override_config.yaml +++ b/pkgs/ffigen/test/native_objc_test/bad_override_config.yaml @@ -5,6 +5,10 @@ output: 'bad_override_bindings.dart' exclude-all-by-default: true objc-interfaces: include: + - Polygon + - Triangle + - Rectangle + - Square - BadOverrideGrandparent - BadOverrideParent - BadOverrideUncle diff --git a/pkgs/ffigen/test/native_objc_test/bad_override_test.dart b/pkgs/ffigen/test/native_objc_test/bad_override_test.dart index 04c94f851f..9be1a9d4a8 100644 --- a/pkgs/ffigen/test/native_objc_test/bad_override_test.dart +++ b/pkgs/ffigen/test/native_objc_test/bad_override_test.dart @@ -47,5 +47,32 @@ void main() { // Uncle isn't affected by the transform, so has an ordinary method. expect(BadOverrideUncle.new1().methodVsGetter(), 2); }); + + test('Contravariant returns', () { + // Return types are supposed to be covariant, but ObjC allows them to be + // contravariant. + // https://github.com/dart-lang/native/issues/1220 + Polygon parentResult = BadOverrideParent.new1().contravariantReturn(); + expect(parentResult.name().toString(), 'Rectangle'); + + Polygon childResult = BadOverrideChild.new1().contravariantReturn(); + expect(childResult.name().toString(), 'Triangle'); + }); + + test('Covariant args', () { + // Arg types are supposed to be contravariant, but ObjC allows them to be + // covariant. + // https://github.com/dart-lang/native/issues/1220 + final square = Square.new1(); + final triangle = Triangle.new1(); + + var parent = BadOverrideParent.new1(); + expect(parent.covariantArg_(square).toString(), 'Polygon: Square'); + expect(parent.covariantArg_(triangle).toString(), 'Polygon: Triangle'); + + parent = BadOverrideChild.new1(); + expect(parent.covariantArg_(square).toString(), 'Rectangle: Square'); + expect(() => parent.covariantArg_(triangle), throwsA(isA())); + }); }); } diff --git a/pkgs/ffigen/test/native_objc_test/bad_override_test.m b/pkgs/ffigen/test/native_objc_test/bad_override_test.m index ceed0cecbf..12376721f7 100644 --- a/pkgs/ffigen/test/native_objc_test/bad_override_test.m +++ b/pkgs/ffigen/test/native_objc_test/bad_override_test.m @@ -3,6 +3,37 @@ // BSD-style license that can be found in the LICENSE file. #import +#import + +@interface Polygon : NSObject {} +-(NSString*)name; +@end +@implementation Polygon +-(NSString*)name { return @"Polygon"; } +@end + +@interface Triangle : Polygon {} +-(NSString*)name; +@end +@implementation Triangle +-(NSString*)name { return @"Triangle"; } +@end + +@interface Rectangle : Polygon {} +-(NSString*)name; +@end +@implementation Rectangle +-(NSString*)name { return @"Rectangle"; } +@end + +@interface Square : Rectangle {} +-(NSString*)name; +@end +@implementation Square +-(NSString*)name { return @"Square"; } +@end + + @interface BadOverrideGrandparent : NSObject {} @end @@ -11,10 +42,16 @@ @implementation BadOverrideGrandparent @interface BadOverrideParent : BadOverrideGrandparent {} -(int32_t)methodVsGetter; -@property (readonly) int32_t methodVsGetter; +-(Rectangle*)contravariantReturn; +-(NSString*)covariantArg:(Polygon*)poly; @end @implementation BadOverrideParent -(int32_t)methodVsGetter { return 1; } +-(Rectangle*)contravariantReturn { return [Rectangle new]; } + +-(NSString*)covariantArg:(Polygon*)poly { + return [@"Polygon: " stringByAppendingString: [poly name]]; +} @end @interface BadOverrideUncle : BadOverrideGrandparent {} @@ -31,9 +68,16 @@ @implementation BadOverrideAunt @interface BadOverrideChild : BadOverrideParent {} @property (readonly) int32_t methodVsGetter; +-(Polygon*)contravariantReturn; +-(NSString*)covariantArg:(Rectangle*)rect; @end @implementation BadOverrideChild -(int32_t)methodVsGetter { return 11; } +-(Polygon*)contravariantReturn { return [Triangle new]; } + +-(NSString*)covariantArg:(Rectangle*)rect { + return [@"Rectangle: " stringByAppendingString: [rect name]]; +} @end @interface BadOverrideSibbling : BadOverrideParent {} diff --git a/pkgs/ffigen/test/unit_tests/subtyping_test.dart b/pkgs/ffigen/test/unit_tests/subtyping_test.dart new file mode 100644 index 0000000000..5ac548ca83 --- /dev/null +++ b/pkgs/ffigen/test/unit_tests/subtyping_test.dart @@ -0,0 +1,241 @@ +// Copyright (c) 2024, 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 'package:test/test.dart'; + +void main() { + group('subtyping', () { + final builtInFunctions = ObjCBuiltInFunctions('', false); + + ObjCInterface makeInterface(String name, ObjCInterface? superType, + [List methods = const []]) { + final itf = ObjCInterface( + usr: name, + originalName: name, + builtInFunctions: builtInFunctions, + ); + if (superType != null) { + itf.superType = superType; + superType.subtypes.add(itf); + } + for (final m in methods) { + itf.addMethod(m); + } + itf.filled = true; + return itf; + } + + final grandparent = makeInterface('Grandparent', null); + final parent = makeInterface('Parent', grandparent); + final uncle = makeInterface('Uncle', grandparent); + final child = makeInterface('Child', parent); + + group('ObjCInterface', () { + test('subtype', () { + expect(parent.isSubtypeOf(parent), isTrue); + expect(child.isSubtypeOf(child), isTrue); + + expect(parent.isSubtypeOf(grandparent), isTrue); + expect(grandparent.isSubtypeOf(parent), isFalse); + + expect(child.isSubtypeOf(grandparent), isTrue); + expect(grandparent.isSubtypeOf(child), isFalse); + + expect(child.isSubtypeOf(uncle), isFalse); + expect(uncle.isSubtypeOf(child), isFalse); + }); + + test('supertype', () { + expect(parent.isSupertypeOf(parent), isTrue); + expect(child.isSupertypeOf(child), isTrue); + + expect(parent.isSupertypeOf(grandparent), isFalse); + expect(grandparent.isSupertypeOf(parent), isTrue); + + expect(child.isSupertypeOf(grandparent), isFalse); + expect(grandparent.isSupertypeOf(child), isTrue); + + expect(child.isSupertypeOf(uncle), isFalse); + expect(uncle.isSupertypeOf(child), isFalse); + }); + }); + + group('FunctionType', () { + FunctionType makeFunc(Type returnType, List argTypes) => + FunctionType(returnType: returnType, parameters: [ + for (final t in argTypes) Parameter(type: t, objCConsumed: false), + ]); + + test('covariant returns', () { + // Return types are covariant. S Function() <: T Function() if S <: T. + final returnsParent = makeFunc(parent, []); + final returnsChild = makeFunc(child, []); + + expect(returnsParent.isSubtypeOf(returnsParent), isTrue); + expect(returnsChild.isSubtypeOf(returnsChild), isTrue); + expect(returnsChild.isSubtypeOf(returnsParent), isTrue); + expect(returnsParent.isSubtypeOf(returnsChild), isFalse); + }); + + test('contravariant args', () { + // Arg types are contravariant. Function(S) <: Function(T) if T <: S. + final acceptsParent = makeFunc(voidType, [parent]); + final acceptsChild = makeFunc(voidType, [child]); + + expect(acceptsParent.isSubtypeOf(acceptsParent), isTrue); + expect(acceptsChild.isSubtypeOf(acceptsChild), isTrue); + expect(acceptsChild.isSubtypeOf(acceptsParent), isFalse); + expect(acceptsParent.isSubtypeOf(acceptsChild), isTrue); + }); + + test('multiple args', () { + expect( + makeFunc(voidType, [parent, parent]) + .isSubtypeOf(makeFunc(voidType, [parent, parent])), + isTrue); + expect( + makeFunc(voidType, [parent, parent]) + .isSubtypeOf(makeFunc(voidType, [child, child])), + isTrue); + expect( + makeFunc(voidType, [child, child]) + .isSubtypeOf(makeFunc(voidType, [parent, parent])), + isFalse); + expect( + makeFunc(voidType, [child, parent]) + .isSubtypeOf(makeFunc(voidType, [parent, child])), + isFalse); + expect( + makeFunc(voidType, [parent, parent, parent]) + .isSubtypeOf(makeFunc(voidType, [child, child])), + isFalse); + expect( + makeFunc(voidType, [parent]) + .isSubtypeOf(makeFunc(voidType, [child, child])), + isFalse); + }); + + test('args and returns', () { + expect(makeFunc(child, [parent]).isSubtypeOf(makeFunc(parent, [child])), + isTrue); + expect(makeFunc(parent, [parent]).isSubtypeOf(makeFunc(child, [child])), + isFalse); + expect(makeFunc(child, [child]).isSubtypeOf(makeFunc(parent, [parent])), + isFalse); + expect(makeFunc(parent, [child]).isSubtypeOf(makeFunc(child, [parent])), + isFalse); + }); + + test('NativeFunc', () { + final returnsParent = NativeFunc(makeFunc(parent, [])); + final returnsChild = NativeFunc(makeFunc(child, [])); + + expect(returnsParent.isSubtypeOf(returnsParent), isTrue); + expect(returnsChild.isSubtypeOf(returnsChild), isTrue); + expect(returnsChild.isSubtypeOf(returnsParent), isTrue); + expect(returnsParent.isSubtypeOf(returnsChild), isFalse); + }); + }); + + group('ObjCBlock', () { + ObjCBlock makeBlock(Type returnType, List argTypes) => ObjCBlock( + returnType: returnType, + params: [ + for (final t in argTypes) Parameter(type: t, objCConsumed: false), + ], + returnsRetained: false, + builtInFunctions: builtInFunctions); + + test('covariant returns', () { + // Return types are covariant. S Function() <: T Function() if S <: T. + final returnsParent = makeBlock(parent, []); + final returnsChild = makeBlock(child, []); + + expect(returnsParent.isSubtypeOf(returnsParent), isTrue); + expect(returnsChild.isSubtypeOf(returnsChild), isTrue); + expect(returnsChild.isSubtypeOf(returnsParent), isTrue); + expect(returnsParent.isSubtypeOf(returnsChild), isFalse); + }); + + test('contravariant args', () { + // Arg types are contravariant. Function(S) <: Function(T) if T <: S. + final acceptsParent = makeBlock(voidType, [parent]); + final acceptsChild = makeBlock(voidType, [child]); + + expect(acceptsParent.isSubtypeOf(acceptsParent), isTrue); + expect(acceptsChild.isSubtypeOf(acceptsChild), isTrue); + expect(acceptsChild.isSubtypeOf(acceptsParent), isFalse); + expect(acceptsParent.isSubtypeOf(acceptsChild), isTrue); + }); + + test('multiple args', () { + expect( + makeBlock(voidType, [parent, parent]) + .isSubtypeOf(makeBlock(voidType, [parent, parent])), + isTrue); + expect( + makeBlock(voidType, [parent, parent]) + .isSubtypeOf(makeBlock(voidType, [child, child])), + isTrue); + expect( + makeBlock(voidType, [child, child]) + .isSubtypeOf(makeBlock(voidType, [parent, parent])), + isFalse); + expect( + makeBlock(voidType, [child, parent]) + .isSubtypeOf(makeBlock(voidType, [parent, child])), + isFalse); + expect( + makeBlock(voidType, [parent, parent, parent]) + .isSubtypeOf(makeBlock(voidType, [child, child])), + isFalse); + expect( + makeBlock(voidType, [parent]) + .isSubtypeOf(makeBlock(voidType, [child, child])), + isFalse); + }); + + test('args and returns', () { + expect( + makeBlock(child, [parent]).isSubtypeOf(makeBlock(parent, [child])), + isTrue); + expect( + makeBlock(parent, [parent]).isSubtypeOf(makeBlock(child, [child])), + isFalse); + expect( + makeBlock(child, [child]).isSubtypeOf(makeBlock(parent, [parent])), + isFalse); + expect( + makeBlock(parent, [child]).isSubtypeOf(makeBlock(child, [parent])), + isFalse); + }); + }); + + test('ObjCNullable', () { + expect(ObjCNullable(parent).isSubtypeOf(ObjCNullable(parent)), isTrue); + expect(ObjCNullable(child).isSubtypeOf(ObjCNullable(parent)), isTrue); + expect(ObjCNullable(parent).isSubtypeOf(ObjCNullable(child)), isFalse); + expect(parent.isSubtypeOf(ObjCNullable(parent)), isTrue); + expect(ObjCNullable(parent).isSubtypeOf(parent), isFalse); + expect(child.isSubtypeOf(ObjCNullable(parent)), isTrue); + expect(ObjCNullable(child).isSubtypeOf(parent), isFalse); + expect(ObjCNullable(parent).isSubtypeOf(child), isFalse); + expect(parent.isSubtypeOf(ObjCNullable(child)), isFalse); + }); + + test('Typealias', () { + Typealias makeTypealias(Type t) => Typealias(name: '', type: t); + expect(makeTypealias(parent).isSubtypeOf(makeTypealias(parent)), isTrue); + expect(makeTypealias(child).isSubtypeOf(makeTypealias(parent)), isTrue); + expect(makeTypealias(parent).isSubtypeOf(makeTypealias(child)), isFalse); + expect(parent.isSubtypeOf(makeTypealias(parent)), isTrue); + expect(makeTypealias(parent).isSubtypeOf(parent), isTrue); + expect(child.isSubtypeOf(makeTypealias(parent)), isTrue); + expect(makeTypealias(child).isSubtypeOf(parent), isTrue); + expect(makeTypealias(parent).isSubtypeOf(child), isFalse); + expect(parent.isSubtypeOf(makeTypealias(child)), isFalse); + }); + }); +} diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index a8117be61e..67b86779e3 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -412,7 +412,7 @@ class NSArray extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSArray, _sel_supportsSecureCoding); } @@ -654,7 +654,7 @@ class NSCharacterSet extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSCharacterSet, _sel_supportsSecureCoding); } @@ -930,7 +930,7 @@ class NSData extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSData, _sel_supportsSecureCoding); } @@ -1305,7 +1305,7 @@ class NSDate extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSDate, _sel_supportsSecureCoding); } @@ -1485,7 +1485,7 @@ class NSDictionary extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSDictionary, _sel_supportsSecureCoding); } @@ -1709,7 +1709,7 @@ class NSError extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSError, _sel_supportsSecureCoding); } @@ -1944,7 +1944,7 @@ class NSIndexSet extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSIndexSet, _sel_supportsSecureCoding); } @@ -2627,7 +2627,7 @@ class NSMutableArray extends NSArray { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableArray, _sel_supportsSecureCoding); } @@ -2860,7 +2860,7 @@ class NSMutableData extends NSData { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableData, _sel_supportsSecureCoding); } @@ -3152,7 +3152,7 @@ class NSMutableDictionary extends NSDictionary { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableDictionary, _sel_supportsSecureCoding); } @@ -3305,7 +3305,7 @@ class NSMutableIndexSet extends NSIndexSet { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableIndexSet, _sel_supportsSecureCoding); } @@ -3540,7 +3540,7 @@ class NSMutableOrderedSet extends NSOrderedSet { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableOrderedSet, _sel_supportsSecureCoding); } @@ -3793,7 +3793,7 @@ class NSMutableSet extends NSSet { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSMutableSet, _sel_supportsSecureCoding); } @@ -4075,7 +4075,7 @@ class NSMutableString extends NSString { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635( _class_NSMutableString, _sel_supportsSecureCoding); } @@ -4493,7 +4493,7 @@ class NSNumber extends NSValue { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSNumber, _sel_supportsSecureCoding); } @@ -4901,7 +4901,7 @@ class NSObject extends objc.ObjCObjectBase { } /// debugDescription - NSString debugDescription1() { + NSString get debugDescription1 { if (!objc.respondsToSelector(this.ref.pointer, _sel_debugDescription)) { throw objc.UnimplementedOptionalMethodException( 'NSObject', 'debugDescription'); @@ -4910,6 +4910,12 @@ class NSObject extends objc.ObjCObjectBase { return NSString.castFromPointer(_ret, retain: true, release: true); } + /// description + NSString get description1 { + final _ret = _objc_msgSend_1x359cv(this.ref.pointer, _sel_description); + return NSString.castFromPointer(_ret, retain: true, release: true); + } + /// doesNotRecognizeSelector: void doesNotRecognizeSelector_(ffi.Pointer aSelector) { _objc_msgSend_1d9e4oe( @@ -4930,6 +4936,11 @@ class NSObject extends objc.ObjCObjectBase { return objc.ObjCObjectBase(_ret, retain: true, release: true); } + /// hash + int get hash1 { + return _objc_msgSend_xw2lbc(this.ref.pointer, _sel_hash); + } + /// init NSObject init() { final _ret = @@ -5040,7 +5051,7 @@ class NSObject extends objc.ObjCObjectBase { } /// superclass - objc.ObjCObjectBase superclass1() { + objc.ObjCObjectBase get superclass1 { final _ret = _objc_msgSend_1x359cv(this.ref.pointer, _sel_superclass); return objc.ObjCObjectBase(_ret, retain: true, release: true); } @@ -5328,7 +5339,7 @@ class NSOrderedSet extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSOrderedSet, _sel_supportsSecureCoding); } @@ -5862,7 +5873,7 @@ class NSSet extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSSet, _sel_supportsSecureCoding); } @@ -6426,7 +6437,7 @@ class NSString extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSString, _sel_supportsSecureCoding); } @@ -7735,7 +7746,7 @@ class NSURL extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSURL, _sel_supportsSecureCoding); } @@ -8335,7 +8346,7 @@ class NSValue extends NSObject { } /// supportsSecureCoding - static bool supportsSecureCoding() { + static bool getSupportsSecureCoding() { return _objc_msgSend_91o635(_class_NSValue, _sel_supportsSecureCoding); }