// RUN: %target-run-simple-swiftgyb // REQUIRES: executable_test // REQUIRES: reflection // FIXME(id-as-any): make Swift errors boxed in NSError eagerly bridged. // FIXME(id-as-any): add tests for unboxing. // FIXME(id-as-any): add tests for the _ObjectiveCBridgeable conformance. %{ import re class SwiftClass(object): def __init__(self, full_name): m = re.match(r'([A-Za-z0-9_]+)(<(.+)>)?', full_name) self.type_name = m.group(1) if m.group(3) is not None: self.generic_parameters_list = [m.group(3)] else: self.generic_parameters_list = [] @property def is_generic(self): return len(self.generic_parameters_list) != 0 @property def generic_parameters_decl(self): if not self.is_generic: return '' assert len(self.generic_parameters_list) == 1 return '<%s>' % (self.generic_parameters_list[0]) @property def full_name(self): return '%s%s' % (self.type_name, self.generic_parameters_decl) def specialize_with(self, substitutions): assert len(substitutions) == 1 assert len(self.generic_parameters_list) == 1 return SwiftClassSpecialization( '%s<%s>' % ( self.type_name, substitutions[self.generic_parameters_list[0]])) class SwiftClassSpecialization(object): def __init__(self, full_name): m = re.match(r'(.+)(<(.+)>)?', full_name) self.type_name = m.group(1) self.full_name = full_name }% import StdlibUnittest #if _runtime(_ObjC) import Foundation #endif let AnyHashableTests = TestSuite("AnyHashableTests") AnyHashableTests.test("CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable") { let v = AnyHashable(CustomPrintableValue(1)) expectPrinted("(value: 1).description", v) expectDebugPrinted("AnyHashable((value: 1).debugDescription)", v) expectDumped( "▿ AnyHashable((value: 1).debugDescription)\n" + " ▿ value: (value: 1).debugDescription\n" + " - value: 1\n" + " - identity: 0\n", v) } % for wrapped in ['MinimalHashableValue', 'MinimalHashableClass']: AnyHashableTests.test("AnyHashable(${wrapped})/Hashable") { let xs = (0...5).flatMap { [ ${wrapped}($0, identity: 0), ${wrapped}($0, identity: 1) ] } checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(${wrapped}).base") { let ah = AnyHashable(${wrapped}(42, identity: 0)) expectEqual(${wrapped}.self, type(of: ah.base)) } % end #if _runtime(_ObjC) AnyHashableTests.test("AnyHashable(MinimalHashableValue, SwiftValue(MinimalHashableValue))/Hashable") { let xs = (0...5).flatMap { [ MinimalHashableValue($0, identity: 0), MinimalHashableValue($0, identity: 1) ] } checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) let boxedXs = xs.flatMap { [ AnyHashable($0), AnyHashable(_bridgeAnythingToObjectiveC($0) as! NSObject) ] } for x in boxedXs { expectEqual( "MinimalHashableValue", String(describing: type(of: x.base))) } checkHashable( boxedXs, equalityOracle: { $0 / 4 == $1 / 4 }) } #endif % for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']: % for payload in [ 'OpaqueValue', 'LifetimeTracked' ]: AnyHashableTests.test("AnyHashable(${wrapped}>)/Hashable") { ${wrapped}_equalImpl.value = { ($0 as! ${payload}).value == ($1 as! ${payload}).value } ${wrapped}_hashIntoImpl.value = { $1.combine(($0 as! ${payload}).value) } let xs = (0...5).flatMap { [ ${wrapped}(${payload}($0), identity: 0), ${wrapped}(${payload}($0), identity: 1) ] } checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(${wrapped}).base") { let ah = AnyHashable(${wrapped}(${payload}(42), identity: 0)) expectEqual(${wrapped}<${payload}>.self, type(of: ah.base)) } % end % end AnyHashableTests.test("AnyHashable(mixed minimal hashables)/Hashable") { var xs: [AnyHashable] = [] % for wrapped in ['MinimalHashableValue', 'MinimalHashableClass']: xs += (0..<6).flatMap { [ ${wrapped}($0, identity: 0), ${wrapped}($0, identity: 1) ].map(AnyHashable.init) } % end % for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']: ${wrapped}_equalImpl.value = { (lhs, rhs) in if let lhs = lhs as? OpaqueValue, let rhs = rhs as? OpaqueValue { return lhs.value == rhs.value } return (lhs as! LifetimeTracked) == (rhs as! LifetimeTracked) } ${wrapped}_hashIntoImpl.value = { payload, hasher in if let x = payload as? OpaqueValue { hasher.combine(x.value) return } hasher.combine((payload as! LifetimeTracked).value) } % end % for wrapped in ['GenericMinimalHashableValue', 'GenericMinimalHashableClass']: % for payload in [ 'OpaqueValue', 'LifetimeTracked' ]: xs += (0..<6).flatMap { [ ${wrapped}(${payload}($0), identity: 0), ${wrapped}(${payload}($0), identity: 1) ].map(AnyHashable.init) } % end % end checkHashable( xs, equalityOracle: { $0 / 2 == $1 / 2 }, // FIXME: Types that hash the same way will produce hash collisions when // converted to AnyHashable. Arguably, the type id should be used as a hash // discriminator. hashEqualityOracle: { $0 / 2 % 6 == $1 / 2 % 6 }) } % for (kw, name) in [ % ('class', 'Class'), % ('struct', 'PODStruct'), % ('struct', 'RCStruct'), % ]: ${kw} HasCustomRepresentation_${name} : Hashable, _HasCustomAnyHashableRepresentation { % if name == 'RCStruct': var lifetimeTrackedValue: LifetimeTracked var value: Int { return lifetimeTrackedValue.value } % else: var value: Int % end var identity: Int var hasDefaultAnyHashableRepresentation: Bool init(_ value: Int, identity: Int, hasDefaultAnyHashableRepresentation: Bool) { % if name == 'RCStruct': self.lifetimeTrackedValue = LifetimeTracked(value) % else: self.value = value % end self.identity = identity self.hasDefaultAnyHashableRepresentation = hasDefaultAnyHashableRepresentation } var hashValue: Int { return value } func hash(into hasher: inout Hasher) { hasher.combine(value) } func _toCustomAnyHashable() -> AnyHashable? { if hasDefaultAnyHashableRepresentation { return nil } let customRepresentation = MinimalHashableValue(value, identity: identity) return AnyHashable(customRepresentation) } static func == ( lhs: HasCustomRepresentation_${name}, rhs: HasCustomRepresentation_${name} ) -> Bool { return lhs.value == rhs.value } } ${kw} HasCustomRepresentation_Generic${name} : Hashable, _HasCustomAnyHashableRepresentation { var value: Wrapped var identity: Int var hasDefaultAnyHashableRepresentation: Bool init( _ value: Wrapped, identity: Int, hasDefaultAnyHashableRepresentation: Bool ) { self.value = value self.identity = identity self.hasDefaultAnyHashableRepresentation = hasDefaultAnyHashableRepresentation } var hashValue: Int { return asGenericMinimalHashableValue.hashValue } func hash(into hasher: inout Hasher) { hasher.combine(asGenericMinimalHashableValue) } func _toCustomAnyHashable() -> AnyHashable? { if hasDefaultAnyHashableRepresentation { return nil } let customRepresentation = GenericMinimalHashableValue(value, identity: identity) return AnyHashable(customRepresentation) } var asGenericMinimalHashableValue: GenericMinimalHashableValue { return GenericMinimalHashableValue(value, identity: identity) } static func == ( lhs: HasCustomRepresentation_Generic${name}, rhs: HasCustomRepresentation_Generic${name} ) -> Bool { return lhs.asGenericMinimalHashableValue == rhs.asGenericMinimalHashableValue } } % end % for name in [ 'Class', 'PODStruct', 'RCStruct' ]: % wrapped = 'HasCustomRepresentation_%s' % name % genericWrapped = 'HasCustomRepresentation_Generic%s' % name AnyHashableTests.test("AnyHashable(${wrapped})/Hashable") { let xs = (-2...2).flatMap { [ ${wrapped}( $0, identity: 0, hasDefaultAnyHashableRepresentation: $0 < 0), ${wrapped}( $0, identity: 1, hasDefaultAnyHashableRepresentation: $0 < 0) ] } checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(${wrapped}).base") { do { let ah = AnyHashable( ${wrapped}( 42, identity: 1, hasDefaultAnyHashableRepresentation: true)) expectEqual(${wrapped}.self, type(of: ah.base)) } do { let ah = AnyHashable( ${wrapped}( 42, identity: 1, hasDefaultAnyHashableRepresentation: false)) expectEqual(MinimalHashableValue.self, type(of: ah.base)) } } % for payload in [ 'OpaqueValue', 'LifetimeTracked' ]: AnyHashableTests.test("AnyHashable(${genericWrapped}<${payload}>)/Hashable") { GenericMinimalHashableValue_equalImpl.value = { ($0 as! ${payload}).value == ($1 as! ${payload}).value } GenericMinimalHashableValue_hashIntoImpl.value = { v, hasher in hasher.combine((v as! ${payload}).value) } let xs = (-2...2).flatMap { [ ${genericWrapped}( ${payload}($0), identity: 0, hasDefaultAnyHashableRepresentation: $0 < 0), ${genericWrapped}( ${payload}($0), identity: 1, hasDefaultAnyHashableRepresentation: $0 < 0) ] } checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(${genericWrapped}<${payload}>)/Hashable") { do { let ah = AnyHashable( ${genericWrapped}( ${payload}(42), identity: 0, hasDefaultAnyHashableRepresentation: true)) expectEqual(${genericWrapped}<${payload}>.self, type(of: ah.base)) } do { let ah = AnyHashable( ${genericWrapped}( ${payload}(42), identity: 0, hasDefaultAnyHashableRepresentation: false)) expectEqual( GenericMinimalHashableValue<${payload}>.self, type(of: ah.base)) } } % end % end struct HasCustomRepresentationRecursively : Hashable, _HasCustomAnyHashableRepresentation { var value: Int init(_ value: Int) { self.value = value } var hashValue: Int { return value } func hash(into hasher: inout Hasher) { hasher.combine(value) } func _toCustomAnyHashable() -> AnyHashable? { if value == 0 { return AnyHashable(HasCustomRepresentationRecursively(value + 1)) } else { return nil } } static func == ( lhs: HasCustomRepresentationRecursively, rhs: HasCustomRepresentationRecursively ) -> Bool { return lhs.value == rhs.value } } AnyHashableTests.test("AnyHashable containing values with recursive custom representations") { GenericMinimalHashableValue_equalImpl.value = { ($0 as! ${payload}).value == ($1 as! ${payload}).value } GenericMinimalHashableValue_hashIntoImpl.value = { v, hasher in hasher.combine((v as! ${payload}).value) } // If the custom representation has its own custom representation, // we ignore it. let ah = AnyHashable(HasCustomRepresentationRecursively(0)) expectPrinted("HasCustomRepresentationRecursively(value: 1)", ah) expectEqual(HasCustomRepresentationRecursively.self, type(of: ah.base)) } class T2_Base {} class T3_Base {} class T4_Base1 {} class T4_Base : T4_Base1 {} class T5_Base1 {} class T5_Base : T5_Base1 {} class T6_GenericBase {} class T7_GenericBase {} class T8_GenericBase1 {} class T8_GenericBase : T8_GenericBase1 {} class T9_GenericBase1 {} class T9_GenericBase : T9_GenericBase1 {} #if _runtime(_ObjC) class T10_ObjC_Base : NSObject {} class T11_ObjC_GenericBase : NSObject {} #endif % for prefix in [ 'T0', 'T1', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'T8', 'T9', 'T10_ObjC', 'T11_ObjC' ]: % base = None % if prefix in ['T0', 'T1']: % pass % elif prefix in ['T2', 'T3', 'T4', 'T5', 'T10_ObjC']: % base = SwiftClass(prefix + '_Base') % elif prefix in ['T6', 'T7', 'T8', 'T9', 'T11_ObjC']: % base = SwiftClass(prefix + '_GenericBase') % else: % assert False % end % hashable_base = None % if prefix in ['T0', 'T2', 'T4', 'T6', 'T8', 'T10_ObjC']: % hashable_base = SwiftClass(prefix + '_HashableBase') % elif prefix in ['T1', 'T3', 'T5', 'T7', 'T9', 'T11_ObjC']: % hashable_base = SwiftClass(prefix + '_HashableGenericBase') % else: % assert False % end % if base and base.is_generic and not hashable_base.is_generic: % base = base.specialize_with({'T':'Int'}) % end % bases = [] % if base: % bases += [base] % if not 'ObjC' in prefix: % bases += [SwiftClass('Hashable')] % end ${'#if _runtime(_ObjC)' if 'ObjC' in prefix else ''} class ${hashable_base.full_name} : ${', '.join([b.full_name for b in bases])} { var value: Int init(_ value: Int) { self.value = value } % if 'ObjC' in prefix: override var hash: Int { return value } override func isEqual(_ object: Any?) -> Bool { guard let rhs = object as? ${hashable_base.full_name} else { return false } return self.value == rhs.value } % else: var hashValue: Int { return value } func hash(into hasher: inout Hasher) { hasher.combine(value) } static func == ${hashable_base.generic_parameters_decl} ( lhs: ${hashable_base.full_name}, rhs: ${hashable_base.full_name} ) -> Bool { return lhs.value == rhs.value } % end } %{ generic = hashable_base.generic_parameters_decl derivedA = SwiftClass(prefix + '_DerivedA' + generic) derivedB = SwiftClass(prefix + '_DerivedB' + generic) derivedAA = SwiftClass(prefix + '_DerivedAA' + generic) derivedAB = SwiftClass(prefix + '_DerivedAB' + generic) derivedBA = SwiftClass(prefix + '_DerivedBA' + generic) derivedBB = SwiftClass(prefix + '_DerivedBB' + generic) derivedAAA = SwiftClass(prefix + '_DerivedAAA' + generic) derivedAAB = SwiftClass(prefix + '_DerivedAAB' + generic) derivedABA = SwiftClass(prefix + '_DerivedABA' + generic) derivedABB = SwiftClass(prefix + '_DerivedABB' + generic) derivedBAA = SwiftClass(prefix + '_DerivedBAA' + generic) derivedBAB = SwiftClass(prefix + '_DerivedBAB' + generic) derivedBBA = SwiftClass(prefix + '_DerivedBBA' + generic) derivedBBB = SwiftClass(prefix + '_DerivedBBB' + generic) types = [ (hashable_base, base), (derivedA, hashable_base), (derivedB, hashable_base), (derivedAA, derivedA), (derivedAB, derivedA), (derivedBA, derivedB), (derivedBB, derivedB), (derivedAAA, derivedAA), (derivedAAB, derivedAA), (derivedABA, derivedAB), (derivedABB, derivedAB), (derivedBAA, derivedBA), (derivedBAB, derivedBA), (derivedBBA, derivedBB), (derivedBBB, derivedBB), ] }% % for (Self, Super) in types: % if 'Base' not in Self.type_name: class ${Self.full_name} : ${Super.full_name} {} % end % end AnyHashableTests.test("AnyHashable containing classes from the ${prefix} hierarchy") { typealias T = Int % bunch = 2 let xs = [ % for (i, (Self, _)) in enumerate(types): % ConcreteSelf = Self % if ConcreteSelf.is_generic: % ConcreteSelf = ConcreteSelf.specialize_with({'T':'Int'}) % end ${Self.full_name}(${bunch + i}), % for j in range(0, bunch): ${Self.full_name}(${j}), % end % end ] func equalityOracle(_ lhs: Int, rhs: Int) -> Bool { if lhs == rhs { return true } let p = ${bunch + 1} if lhs % p == 0 || rhs % p == 0 { return false } return lhs % p == rhs % p } checkHashable(xs, equalityOracle: equalityOracle) let anyHashables = xs.map(AnyHashable.init) checkHashable(anyHashables, equalityOracle: equalityOracle) for (x, ah) in zip(xs, anyHashables) { expectEqual(type(of: x), type(of: ah.base)) } } ${'#endif' if 'ObjC' in prefix else ''} % end #if _runtime(_ObjC) // There is no public way to define new CF types, so we are using // CFBitVector and CFMutableBitVector. extension CFBitVector { static func makeImmutable(from values: Array) -> CFBitVector { return CFBitVectorCreate(/*allocator:*/ nil, values, values.count * 8) } var asArray: Array { var result = [UInt8](repeating: 0, count: CFBitVectorGetCount(self) / 8) CFBitVectorGetBits( self, CFRange(location: 0, length: result.count * 8), &result) return result } } extension CFMutableBitVector { static func makeMutable(from values: Array) -> CFMutableBitVector { return CFBitVectorCreateMutableCopy( /*allocator:*/ nil, /*capacity:*/ 0, CFBitVector.makeImmutable(from: values)) } } let interestingBitVectorArrays: [[UInt8]] = [ [], [0x00], [0xaa], [0xff], [0xff, 0x00], [0x00, 0xff], [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], ] AnyHashableTests.test("AnyHashable(CFBitVector)/Hashable, .base") { let bitVectors: [CFBitVector] = interestingBitVectorArrays.map(CFBitVector.makeImmutable) let hashEqualityOracle: (Int, Int) -> Bool = { // CFBitVector returns its count as the hash. interestingBitVectorArrays[$0].count == interestingBitVectorArrays[$1].count } let arrays = bitVectors.map { $0.asArray } func isEq(_ lhs: [[UInt8]], _ rhs: [[UInt8]]) -> Bool { return zip(lhs, rhs).map { $0 == $1 }.reduce(true, { $0 && $1 }) } expectEqualTest(interestingBitVectorArrays, arrays, sameValue: isEq) checkHashable( bitVectors, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) do { expectEqual("ForeignClass", SwiftRuntime.metadataKind(of: bitVectors.first!)) let anyHashables = bitVectors.map(AnyHashable.init) checkHashable(anyHashables, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) let v = anyHashables.first!.base expectTrue(type(of: v) is CFBitVector.Type) } do { let bitVectorsAsAnyObjects: [NSObject] = bitVectors.map { ($0 as AnyObject) as! NSObject } expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: bitVectorsAsAnyObjects.first!)) let anyHashables = bitVectorsAsAnyObjects.map(AnyHashable.init) checkHashable(anyHashables, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) let v = anyHashables.first!.base expectTrue(type(of: v) is CFBitVector.Type) } } AnyHashableTests.test("AnyHashable(CFMutableBitVector)/Hashable, .base") { // CFMutableBitVector inherits the Hashable conformance from // CFBitVector. let bitVectors: [CFMutableBitVector] = interestingBitVectorArrays.map(CFMutableBitVector.makeMutable) let hashEqualityOracle: (Int, Int) -> Bool = { // CFBitVector returns its count as the hash. interestingBitVectorArrays[$0].count == interestingBitVectorArrays[$1].count } let arrays = bitVectors.map { $0.asArray } func isEq(_ lhs: [[UInt8]], _ rhs: [[UInt8]]) -> Bool { return zip(lhs, rhs).map { $0 == $1 }.reduce(true, { $0 && $1 }) } expectEqualTest(interestingBitVectorArrays, arrays, sameValue: isEq) checkHashable( bitVectors, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) do { expectEqual( "ForeignClass", SwiftRuntime.metadataKind(of: bitVectors.first!)) let anyHashables = bitVectors.map(AnyHashable.init) checkHashable( anyHashables, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) let v = anyHashables.first!.base expectTrue(type(of: v) is CFMutableBitVector.Type) } do { let bitVectorsAsAnyObjects: [NSObject] = bitVectors.map { ($0 as AnyObject) as! NSObject } checkHashable( bitVectorsAsAnyObjects, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: bitVectorsAsAnyObjects.first!)) let anyHashables = bitVectorsAsAnyObjects.map(AnyHashable.init) checkHashable( anyHashables, equalityOracle: { $0 == $1 }, hashEqualityOracle: hashEqualityOracle) let v = anyHashables.first!.base expectTrue(type(of: v) is CFMutableBitVector.Type) } } #endif enum MinimalHashablePODSwiftError : Error, Hashable { case caseA case caseB case caseC } enum MinimalHashableRCSwiftError : Error, Hashable { case caseA(LifetimeTracked) case caseB(LifetimeTracked) case caseC(LifetimeTracked) func hash(into hasher: inout Hasher) { switch self { case .caseA: hasher.combine(10) case .caseB: hasher.combine(20) case .caseC: hasher.combine(30) } } static func == ( lhs: MinimalHashableRCSwiftError, rhs: MinimalHashableRCSwiftError ) -> Bool { switch (lhs, rhs) { case (.caseA(let lhs), .caseA(let rhs)): return lhs == rhs case (.caseB(let lhs), .caseB(let rhs)): return lhs == rhs case (.caseC(let lhs), .caseC(let rhs)): return lhs == rhs default: return false } } } AnyHashableTests.test("AnyHashable(MinimalHashablePODSwiftError)/Hashable") { let xs: [MinimalHashablePODSwiftError] = [ .caseA, .caseA, .caseB, .caseB, .caseC, .caseC, ] expectEqual("Enum", SwiftRuntime.metadataKind(of: xs.first!)) checkHashable(xs, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(MinimalHashablePODSwiftError).base") { let ah = AnyHashable(MinimalHashablePODSwiftError.caseA) expectEqual(MinimalHashablePODSwiftError.self, type(of: ah.base)) } AnyHashableTests.test("AnyHashable(MinimalHashableRCSwiftError)/Hashable") { let xs: [MinimalHashableRCSwiftError] = [ .caseA(LifetimeTracked(1)), .caseA(LifetimeTracked(1)), .caseA(LifetimeTracked(2)), .caseA(LifetimeTracked(2)), .caseB(LifetimeTracked(1)), .caseB(LifetimeTracked(1)), .caseB(LifetimeTracked(2)), .caseB(LifetimeTracked(2)), .caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(2)), .caseC(LifetimeTracked(2)), ] expectEqual("Enum", SwiftRuntime.metadataKind(of: xs.first!)) checkHashable( xs, equalityOracle: { $0 / 2 == $1 / 2 }, hashEqualityOracle: { $0 / 4 == $1 / 4 }) checkHashable( xs.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }, hashEqualityOracle: { $0 / 4 == $1 / 4 }) } AnyHashableTests.test("AnyHashable(MinimalHashableRCSwiftError).base") { let ah = AnyHashable(MinimalHashableRCSwiftError.caseA(LifetimeTracked(1))) expectEqual(MinimalHashableRCSwiftError.self, type(of: ah.base)) } AnyHashableTests.test("AnyHashable(NumericTypes)/Hashable") { // Numeric types holding mathematically equal values must compare equal and // hash the same way when converted to AnyHashable. let groups: [[AnyHashable]] = [ [ 1 as Int, 1 as UInt, 1 as Int8, 1 as UInt8, 1 as Int16, 1 as UInt16, 1 as Int32, 1 as UInt32, 1 as Int64, 1 as UInt64, 1 as Float, 1 as Double, ], [ 42 as Int, 42 as UInt, 42 as Int8, 42 as UInt8, 42 as Int16, 42 as UInt16, 42 as Int32, 42 as UInt32, 42 as Int64, 42 as UInt64, 42 as Float, 42 as Double, ], [ Int(Int32.max), UInt(Int32.max), Int32.max, UInt32(Int32.max), Int64(Int32.max), UInt64(Int32.max), Double(Int32.max), ], [ Float.infinity, Double.infinity, ], [ 0x1.aP1 as Float, // 3.25 0x1.aP1 as Double, ], [ 0x1.a000000000001P1, // 3.25.nextUp, not representable by a Float ] ] checkHashableGroups(groups) } #if !os(Windows) && (arch(i386) || arch(x86_64)) AnyHashableTests.test("AnyHashable(Float80)/Hashable") { let groups: [[AnyHashable]] = [ [ 42 as Int, 42 as Float, 42 as Double, 42 as Float80, ], [ Float.infinity, Double.infinity, Float80.infinity, ], [ 3.25 as Float, 3.25 as Double, 3.25 as Float80, ], [ 0x1.a000000000001P1 as Double, // 3.25.nextUp 0x1.a000000000001P1 as Float80, ], [ 0x1.a000000000000002p1 as Float80, // (3.25 as Float80).nextUp ], ] checkHashableGroups(groups) } #endif #if _runtime(_ObjC) // A wrapper type around an Int that bridges to NSNumber. struct IntWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: Int } // A wrapper type around an Int that bridges to NSNumber. struct IntWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: Int } // A wrapper type around an Int that bridges to NSNumber. struct Int8Wrapper : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: Int8 } AnyHashableTests.test("AnyHashable(IntWrappers)/Hashable") { let groups: [[AnyHashable]] = [ [ IntWrapper1(rawValue: 42), IntWrapper2(rawValue: 42), Int8Wrapper(rawValue: 42), 42, 42 as Double, 42 as NSNumber, ], [ IntWrapper1(rawValue: -23), IntWrapper2(rawValue: -23), Int8Wrapper(rawValue: -23), -23, -23 as Double, -23 as NSNumber, ], ] checkHashableGroups(groups) } #endif #if _runtime(_ObjC) // A wrapper type around a String that bridges to NSString. struct StringWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: String } // A wrapper type around a String that bridges to NSString. struct StringWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: String } AnyHashableTests.test("AnyHashable(StringWrappers)/Hashable") { let groups: [[AnyHashable]] = [ [ StringWrapper1(rawValue: "hello"), StringWrapper2(rawValue: "hello"), "hello" as String, "hello" as NSString, ], [ StringWrapper1(rawValue: "world"), StringWrapper2(rawValue: "world"), "world" as String, "world" as NSString, ] ] checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Set)/Hashable") { let groups: [[AnyHashable]] = [ [ Set([1, 2, 3]), Set([1, 2, 3] as [Int8]), Set([1, 2, 3] as [Float]), NSSet(set: [1, 2, 3]), ], [ Set([2, 3, 4]), NSSet(set: [2, 3, 4]), ], [ Set([Set([1, 2]), Set([3, 4])]), NSSet(set: [NSSet(set: [1, 2]), NSSet(set: [3, 4])]), ], [ Set([Set([1, 3]), Set([2, 4])]), NSSet(set: [NSSet(set: [1, 3]), NSSet(set: [2, 4])]), ], ] checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Array)/Hashable") { let groups: [[AnyHashable]] = [ [ [1, 2, 3], [1, 2, 3] as [Int8], [1, 2, 3] as [Double], NSArray(array: [1, 2, 3]), ], [ [3, 2, 1], [3, 2, 1] as [AnyHashable], NSArray(array: [3, 2, 1]), ], [ [[1, 2], [3, 4]], NSArray(array: [NSArray(array: [1, 2]), NSArray(array: [3, 4])]), ], [ [[3, 4], [1, 2]], NSArray(array: [NSArray(array: [3, 4]), NSArray(array: [1, 2])]), ] ] checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Dictionary)/Hashable") { let groups: [[AnyHashable]] = [ [ ["hello": 1, "world": 2] as [String: Int], ["hello": 1, "world": 2] as [String: Int16], ["hello": 1, "world": 2] as [String: Float], NSDictionary(dictionary: ["hello": 1, "world": 2]), ], [ ["hello": 2, "world": 1], NSDictionary(dictionary: ["hello": 2, "world": 1]), ], [ ["hello": ["foo": 1, "bar": 2], "world": ["foo": 2, "bar": 1]], NSDictionary(dictionary: [ "hello": ["foo": 1, "bar": 2], "world": ["foo": 2, "bar": 1]]), ], [ ["hello": ["foo": 2, "bar": 1], "world": ["foo": 1, "bar": 2]], NSDictionary(dictionary: [ "hello": ["foo": 2, "bar": 1], "world": ["foo": 1, "bar": 2]]), ], ] checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(__SwiftNativeNSError(MinimalHashablePODSwiftError))/Hashable") { let swiftErrors: [MinimalHashablePODSwiftError] = [ .caseA, .caseA, .caseB, .caseB, .caseC, .caseC, ] let nsErrors: [NSError] = swiftErrors.flatMap { swiftError -> [NSError] in let bridgedNSError = swiftError as NSError return [ bridgedNSError, NSError(domain: bridgedNSError.domain, code: bridgedNSError.code) ] } expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: nsErrors[0])) expectEqual("__SwiftNativeNSError", String(describing: type(of: nsErrors[0]))) func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { // Swift errors compare equal to the `NSError`s that have the same domain // and code. return lhs / 4 == rhs / 4 } checkHashable(nsErrors, equalityOracle: equalityOracle) checkHashable( nsErrors.map(AnyHashable.init), equalityOracle: equalityOracle) // FIXME(id-as-any): run `checkHashable` on an array of mixed // `AnyHashable(MinimalHashablePODSwiftError)` and // `AnyHashable(__SwiftNativeNSError(MinimalHashablePODSwiftError))`. For // this to succeed, we need to eagerly bridge Swift errors into the Swift // representation when wrapped in `AnyHashable`. } AnyHashableTests.test("AnyHashable(__SwiftNativeNSError(MinimalHashablePODSwiftError)).base") { let ah = AnyHashable(MinimalHashablePODSwiftError.caseA as NSError) expectTrue(type(of: ah.base) is NSError.Type) } AnyHashableTests.test("AnyHashable(__SwiftNativeNSError(MinimalHashableRCSwiftError))/Hashable") { let swiftErrors: [MinimalHashableRCSwiftError] = [ .caseA(LifetimeTracked(1)), .caseA(LifetimeTracked(1)), .caseA(LifetimeTracked(2)), .caseA(LifetimeTracked(2)), .caseB(LifetimeTracked(1)), .caseB(LifetimeTracked(1)), .caseB(LifetimeTracked(2)), .caseB(LifetimeTracked(2)), .caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(1)), .caseC(LifetimeTracked(2)), .caseC(LifetimeTracked(2)), ] checkHashable( swiftErrors, equalityOracle: { $0 / 2 == $1 / 2 }, hashEqualityOracle: { $0 / 4 == $1 / 4 }, allowBrokenTransitivity: false) let nsErrors: [NSError] = swiftErrors.flatMap { swiftError -> [NSError] in let bridgedNSError = swiftError as NSError return [ bridgedNSError, NSError(domain: bridgedNSError.domain, code: bridgedNSError.code) ] } expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: nsErrors[0])) expectEqual("__SwiftNativeNSError", String(describing: type(of: nsErrors[0]))) expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: nsErrors[1])) expectEqual("NSError", String(describing: type(of: nsErrors[1]))) func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { // Equality of bridged Swift errors takes the payload into account, so for // a fixed X, Y, all `.caseX(LifetimeTracked(Y))` compare equal. // They also compare equal to the `NSError`s that have the same domain // and code. if lhs / 4 == rhs / 4 { return true } // `NSError`s that have the same domain and code as a bridged Swift // error compare equal to all Swift errors with the same domain and code // regardless of the payload. if (lhs % 2 == 1 || rhs % 2 == 1) && (lhs / 8 == rhs / 8) { return true } return false } func hashEqualityOracle(_ lhs: Int, _ rhs: Int) -> Bool { // Every NSError that has the same domain and code hash the same way. return lhs / 8 == rhs / 8 } // FIXME: transitivity is broken because pure `NSError`s can compare equal to // Swift errors with payloads just based on the domain and code, and Swift // errors with payloads don't compare equal when payloads differ. checkHashable( nsErrors, equalityOracle: equalityOracle, hashEqualityOracle: hashEqualityOracle, allowBrokenTransitivity: true) checkHashable( nsErrors.map(AnyHashable.init), equalityOracle: equalityOracle, hashEqualityOracle: hashEqualityOracle, allowBrokenTransitivity: true) // FIXME(id-as-any): run `checkHashable` on an array of mixed // `AnyHashable(MinimalHashableRCSwiftError)` and // `AnyHashable(__SwiftNativeNSError(MinimalHashableRCSwiftError))`. For // this to succeed, we need to eagerly bridge Swift errors into the Swift // representation when wrapped in `AnyHashable`. } AnyHashableTests.test("AnyHashable(__SwiftNativeNSError(MinimalHashableRCSwiftError)).base") { let ah = AnyHashable( MinimalHashableRCSwiftError.caseA(LifetimeTracked(1)) as NSError) expectTrue(type(of: ah.base) is NSError.Type) } AnyHashableTests.test("AnyHashable(NSError)/Hashable") { let nsErrors: [NSError] = [ NSError(domain: "Foo", code: 0), NSError(domain: "Foo", code: 0), NSError(domain: "Foo", code: 1), NSError(domain: "Foo", code: 1), NSError(domain: "Foo", code: 2), NSError(domain: "Foo", code: 2), ] expectEqual( "ObjCClassWrapper", SwiftRuntime.metadataKind(of: nsErrors.first!)) checkHashable(nsErrors, equalityOracle: { $0 / 2 == $1 / 2 }) checkHashable( nsErrors.map(AnyHashable.init), equalityOracle: { $0 / 2 == $1 / 2 }) } AnyHashableTests.test("AnyHashable(NSError).base") { let ah = AnyHashable(NSError(domain: "Foo", code: 0)) expectTrue(type(of: ah.base) is NSError.Type) } #endif runAllTests()