diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift b/stdlib/private/StdlibUnittest/StdlibUnittest.swift index 99798f3ab1289..71b4f60a2bd2b 100644 --- a/stdlib/private/StdlibUnittest/StdlibUnittest.swift +++ b/stdlib/private/StdlibUnittest/StdlibUnittest.swift @@ -2410,6 +2410,33 @@ internal func hash(_ value: H, seed: Int? = nil) -> Int { return hasher.finalize() } +/// Test that the elements of `groups` consist of instances that satisfy the +/// semantic requirements of `Hashable`, with each group defining a distinct +/// equivalence class under `==`. +public func checkHashableGroups( + _ groups: Groups, + _ message: @autoclosure () -> String = "", + stackTrace: SourceLocStack = SourceLocStack(), + showFrame: Bool = true, + file: String = #file, line: UInt = #line +) where Groups.Element: Collection, Groups.Element.Element: Hashable { + let instances = groups.flatMap { $0 } + // groupIndices[i] is the index of the element in groups that contains + // instances[i]. + let groupIndices = + zip(0..., groups).flatMap { i, group in group.map { _ in i } } + func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { + return groupIndices[lhs] == groupIndices[rhs] + } + checkHashable( + instances, + equalityOracle: equalityOracle, + hashEqualityOracle: equalityOracle, + allowBrokenTransitivity: false, + stackTrace: stackTrace.pushIf(showFrame, file: file, line: line), + showFrame: false) +} + /// Test that the elements of `instances` satisfy the semantic requirements of /// `Hashable`, using `equalityOracle` to generate equality and hashing /// expectations from pairs of positions in `instances`. diff --git a/stdlib/public/core/AnyHashable.swift b/stdlib/public/core/AnyHashable.swift index 56dc92d87c44c..23b1341be9a4b 100644 --- a/stdlib/public/core/AnyHashable.swift +++ b/stdlib/public/core/AnyHashable.swift @@ -39,20 +39,29 @@ public protocol _HasCustomAnyHashableRepresentation { @usableFromInline // FIXME(sil-serialize-all) internal protocol _AnyHashableBox { - func _unbox() -> T? + var _canonicalBox: _AnyHashableBox { get } /// Determine whether values in the boxes are equivalent. /// + /// - Precondition: `self` and `box` are in canonical form. /// - Returns: `nil` to indicate that the boxes store different types, so /// no comparison is possible. Otherwise, contains the result of `==`. - func _isEqual(to: _AnyHashableBox) -> Bool? + func _isEqual(to box: _AnyHashableBox) -> Bool? var _hashValue: Int { get } func _hash(into hasher: inout Hasher) + func _rawHashValue(_seed: (UInt64, UInt64)) -> Int var _base: Any { get } + func _unbox() -> T? func _downCastConditional(into result: UnsafeMutablePointer) -> Bool } +extension _AnyHashableBox { + var _canonicalBox: _AnyHashableBox { + return self + } +} + @_fixed_layout // FIXME(sil-serialize-all) @usableFromInline // FIXME(sil-serialize-all) internal struct _ConcreteHashableBox : _AnyHashableBox { @@ -87,6 +96,11 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { _baseHashable.hash(into: &hasher) } + @inlinable // FIXME(sil-serialize-all) + func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + return _baseHashable._rawHashValue(seed: _seed) + } + @inlinable // FIXME(sil-serialize-all) internal var _base: Any { return _baseHashable @@ -101,19 +115,6 @@ internal struct _ConcreteHashableBox : _AnyHashableBox { } } -#if _runtime(_ObjC) -// Retrieve the custom AnyHashable representation of the value after it -// has been bridged to Objective-C. This mapping to Objective-C and back -// turns a non-custom representation into a custom one, which is used as -// the lowest-common-denominator for comparisons. -@inlinable // FIXME(sil-serialize-all) -internal func _getBridgedCustomAnyHashable(_ value: T) -> AnyHashable? { - let bridgedValue = _bridgeAnythingToObjectiveC(value) - return (bridgedValue as? - _HasCustomAnyHashableRepresentation)?._toCustomAnyHashable() -} -#endif - /// A type-erased hashable value. /// /// The `AnyHashable` type forwards equality comparisons and hashing operations @@ -137,8 +138,11 @@ internal func _getBridgedCustomAnyHashable(_ value: T) -> AnyHashable? { public struct AnyHashable { @usableFromInline // FIXME(sil-serialize-all) internal var _box: _AnyHashableBox - @usableFromInline // FIXME(sil-serialize-all) - internal var _usedCustomRepresentation: Bool + + @inlinable // FIXME(sil-serialize-all) + internal init(_box box: _AnyHashableBox) { + self._box = box + } /// Creates a type-erased hashable value that wraps the given instance. /// @@ -160,15 +164,13 @@ public struct AnyHashable { /// - Parameter base: A hashable value to wrap. @inlinable // FIXME(sil-serialize-all) public init(_ base: H) { - if let customRepresentation = + if let custom = (base as? _HasCustomAnyHashableRepresentation)?._toCustomAnyHashable() { - self = customRepresentation - self._usedCustomRepresentation = true + self = custom return } - self._box = _ConcreteHashableBox(0 as Int) - self._usedCustomRepresentation = false + self.init(_box: _ConcreteHashableBox(false)) // Dummy value _makeAnyHashableUpcastingToHashableBaseType( base, storingResultInto: &self) @@ -177,7 +179,6 @@ public struct AnyHashable { @inlinable // FIXME(sil-serialize-all) internal init(_usingDefaultRepresentationOf base: H) { self._box = _ConcreteHashableBox(base) - self._usedCustomRepresentation = false } /// The value wrapped by this instance. @@ -206,13 +207,11 @@ public struct AnyHashable { if _box._downCastConditional(into: result) { return true } #if _runtime(_ObjC) - // If we used a custom representation, bridge to Objective-C and then - // attempt the cast from there. - if _usedCustomRepresentation { - if let value = _bridgeAnythingToObjectiveC(_box._base) as? T { - result.initialize(to: value) - return true - } + // Bridge to Objective-C and then attempt the cast from there. + // FIXME: This should also work without the Objective-C runtime. + if let value = _bridgeAnythingToObjectiveC(_box._base) as? T { + result.initialize(to: value) + return true } #endif @@ -248,34 +247,7 @@ extension AnyHashable : Equatable { /// - rhs: Another type-erased hashable value. @inlinable // FIXME(sil-serialize-all) public static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool { - // If they're equal, we're done. - if let result = lhs._box._isEqual(to: rhs._box) { return result } - - #if _runtime(_ObjC) - // If one used a custom representation but the other did not, bridge - // the one that did *not* use the custom representation to Objective-C: - // if the bridged result has a custom representation, compare those custom - // custom representations. - if lhs._usedCustomRepresentation != rhs._usedCustomRepresentation { - // If the lhs used a custom representation, try comparing against the - // custom representation of the bridged rhs (if there is one). - if lhs._usedCustomRepresentation { - if let customRHS = _getBridgedCustomAnyHashable(rhs._box._base) { - return lhs._box._isEqual(to: customRHS._box) ?? false - } - return false - } - - // Otherwise, try comparing the rhs against the custom representation of - // the bridged lhs (if there is one). - if let customLHS = _getBridgedCustomAnyHashable(lhs._box._base) { - return customLHS._box._isEqual(to: rhs._box) ?? false - } - return false - } - #endif - - return false + return lhs._box._canonicalBox._isEqual(to: rhs._box._canonicalBox) ?? false } } @@ -283,7 +255,7 @@ extension AnyHashable : Hashable { /// The hash value. @inlinable public var hashValue: Int { - return _box._hashValue + return _box._canonicalBox._hashValue } /// Hashes the essential components of this value by feeding them into the @@ -293,7 +265,12 @@ extension AnyHashable : Hashable { /// of this instance. @inlinable public func hash(into hasher: inout Hasher) { - _box._hash(into: &hasher) + _box._canonicalBox._hash(into: &hasher) + } + + @inlinable // FIXME(sil-serialize-all) + public func _rawHashValue(seed: (UInt64, UInt64)) -> Int { + return _box._canonicalBox._rawHashValue(_seed: seed) } } diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index 2fd105716dccb..a23c6d165b105 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -1758,3 +1758,76 @@ extension Array { } } #endif + +extension Array: _HasCustomAnyHashableRepresentation + where Element: Hashable { + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _ArrayAnyHashableBox(self)) + } +} + +internal protocol _ArrayAnyHashableProtocol: _AnyHashableBox { + var count: Int { get } + subscript(index: Int) -> AnyHashable { get } +} + +internal struct _ArrayAnyHashableBox + : _ArrayAnyHashableProtocol { + internal let _value: [Element] + + internal init(_ value: [Element]) { + self._value = value + } + + internal var _base: Any { + return _value + } + + internal var count: Int { + return _value.count + } + + internal subscript(index: Int) -> AnyHashable { + return _value[index] as AnyHashable + } + + func _isEqual(to other: _AnyHashableBox) -> Bool? { + guard let other = other as? _ArrayAnyHashableProtocol else { return nil } + guard _value.count == other.count else { return false } + for i in 0 ..< _value.count { + if self[i] != other[i] { return false } + } + return true + } + + var _hashValue: Int { + var hasher = Hasher() + _hash(into: &hasher) + return hasher.finalize() + } + + func _hash(into hasher: inout Hasher) { + hasher.combine(_value.count) // discriminator + for i in 0 ..< _value.count { + hasher.combine(self[i]) + } + } + + func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + var hasher = Hasher(_seed: _seed) + self._hash(into: &hasher) + return hasher._finalize() + } + + internal func _unbox() -> T? { + return _value as? T + } + + internal func _downCastConditional( + into result: UnsafeMutablePointer + ) -> Bool { + guard let value = _value as? T else { return false } + result.initialize(to: value) + return true + } +} diff --git a/stdlib/public/core/Dictionary.swift b/stdlib/public/core/Dictionary.swift index dd71afe2ccbcc..cc3657b19de74 100644 --- a/stdlib/public/core/Dictionary.swift +++ b/stdlib/public/core/Dictionary.swift @@ -1520,6 +1520,65 @@ extension Dictionary: Hashable where Value: Hashable { } } +extension Dictionary: _HasCustomAnyHashableRepresentation +where Value: Hashable { + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _DictionaryAnyHashableBox(self)) + } +} + +internal struct _DictionaryAnyHashableBox + : _AnyHashableBox { + internal let _value: Dictionary + internal let _canonical: Dictionary + + internal init(_ value: Dictionary) { + self._value = value + self._canonical = value as Dictionary + } + + internal var _base: Any { + return _value + } + + internal var _canonicalBox: _AnyHashableBox { + return _DictionaryAnyHashableBox(_canonical) + } + + internal func _isEqual(to other: _AnyHashableBox) -> Bool? { + guard + let other = other as? _DictionaryAnyHashableBox + else { + return nil + } + return _canonical == other._value + } + + internal var _hashValue: Int { + return _canonical.hashValue + } + + internal func _hash(into hasher: inout Hasher) { + _canonical.hash(into: &hasher) + } + + internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + return _canonical._rawHashValue(seed: _seed) + } + + internal func _unbox() -> T? { + return _value as? T + } + + internal func _downCastConditional( + into result: UnsafeMutablePointer + ) -> Bool { + guard let value = _value as? T else { return false } + result.initialize(to: value) + return true + } +} + extension Dictionary: CustomStringConvertible, CustomDebugStringConvertible { @inlinable // FIXME(sil-serialize-all) internal func _makeDescription() -> String { diff --git a/stdlib/public/core/FloatingPointTypes.swift.gyb b/stdlib/public/core/FloatingPointTypes.swift.gyb index 35311baf34a3d..5936bf2a02b7b 100644 --- a/stdlib/public/core/FloatingPointTypes.swift.gyb +++ b/stdlib/public/core/FloatingPointTypes.swift.gyb @@ -1560,6 +1560,13 @@ extension ${Self} : Hashable { } } +extension ${Self}: _HasCustomAnyHashableRepresentation { + // Not @inlinable + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _${Self}AnyHashableBox(self)) + } +} + extension ${Self} { /// The magnitude of this value. /// @@ -1810,6 +1817,87 @@ extension ${Self} : Strideable { } } +//===----------------------------------------------------------------------===// +// AnyHashable +//===----------------------------------------------------------------------===// + +internal struct _${Self}AnyHashableBox: _AnyHashableBox { + internal typealias Base = ${Self} + internal let _value: Base + + internal init(_ value: Base) { + self._value = value + } + + internal var _canonicalBox: _AnyHashableBox { + // Float and Double are bridged with NSNumber, so we have to follow + // NSNumber's rules for equality. I.e., we need to make sure equal + // numerical values end up in identical boxes after canonicalization, so + // that _isEqual will consider them equal and they're hashed the same way. + // + // Note that these AnyHashable boxes don't currently feed discriminator bits + // to the hasher, so we allow repeatable collisions. E.g., -1 will always + // collide with UInt64.max. + if _value < 0 { + if let i = Int64(exactly: _value) { + return _IntegerAnyHashableBox(i) + } + } else { + if let i = UInt64(exactly: _value) { + return _IntegerAnyHashableBox(i) + } + } + if let d = Double(exactly: _value) { + return _DoubleAnyHashableBox(d) + } + // If a value can't be represented by a Double, keep it in its original + // representation so that it won't compare equal to approximations. (So that + // we don't round off Float80 values.) + return self + } + + internal func _isEqual(to box: _AnyHashableBox) -> Bool? { + _sanityCheck(Int64(exactly: _value) == nil, "self isn't canonical") + _sanityCheck(UInt64(exactly: _value) == nil, "self isn't canonical") + if let box = box as? _${Self}AnyHashableBox { + return _value == box._value + } + return nil + } + + internal var _hashValue: Int { + return _rawHashValue(_seed: Hasher._seed) + } + + internal func _hash(into hasher: inout Hasher) { + _sanityCheck(Int64(exactly: _value) == nil, "self isn't canonical") + _sanityCheck(UInt64(exactly: _value) == nil, "self isn't canonical") + hasher.combine(_value) + } + + internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + var hasher = Hasher(_seed: _seed) + _hash(into: &hasher) + return hasher.finalize() + } + + internal var _base: Any { + return _value + } + + internal func _unbox() -> T? { + return _value as? T + } + + internal func _downCastConditional( + into result: UnsafeMutablePointer + ) -> Bool { + guard let value = _value as? T else { return false } + result.initialize(to: value) + return true + } +} + //===----------------------------------------------------------------------===// // Deprecated operators //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/Hasher.swift b/stdlib/public/core/Hasher.swift index 0aaf1a146fdba..5e48f11eda87b 100644 --- a/stdlib/public/core/Hasher.swift +++ b/stdlib/public/core/Hasher.swift @@ -437,6 +437,7 @@ public struct Hasher { seed: (UInt64, UInt64), bytes value: UInt64, count: Int) -> Int { + _sanityCheck(count >= 0 && count < 8) var core = RawCore(seed: seed) let tbc = _HasherTailBuffer(tail: value, byteCount: count) return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value)) diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 784a5fdb5cf47..31de38b033380 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -3885,41 +3885,31 @@ extension ${Self} : Hashable { /// of this instance. @inlinable public func hash(into hasher: inout Hasher) { - // FIXME(hasher): To correctly bridge `Set`s/`Dictionary`s containing - // `AnyHashable`-boxed integers, all integer values are currently required - // to hash exactly the same way as the corresponding (U)Int64 value. To fix - // this, we should introduce a custom AnyHashable box for integer values - // that sign-extends values to 64 bits. - % if bits <= word_bits: - hasher._combine(_lowWord) - % elif bits == 2 * word_bits: - if let word = ${"" if signed else "U"}Int(exactly: self) { - hasher._combine(word._lowWord) - } else { - hasher._combine(UInt64(_value)) - } - % else: - fatalError("Unsupported integer width") - % end + hasher._combine(${U}${Self}(_value)) } @inlinable public func _rawHashValue(seed: (UInt64, UInt64)) -> Int { - // FIXME(hasher): Note that the AnyHashable concern applies here too, - // because hashValue uses top-level hashing. - % if bits <= word_bits: - return Hasher._hash(seed: seed, _lowWord) - % elif bits == 2 * word_bits: - if let word = ${"" if signed else "U"}Int(exactly: self) { - return Hasher._hash(seed: seed, word._lowWord) - } + % if bits == 64: return Hasher._hash(seed: seed, UInt64(_value)) + % elif bits == word_bits: + return Hasher._hash(seed: seed, UInt(_value)) % else: - fatalError("Unsupported integer width") + return Hasher._hash( + seed: seed, + bytes: UInt64(truncatingIfNeeded: ${U}${Self}(_value)), + count: ${bits / 8}) % end } } +extension ${Self} : _HasCustomAnyHashableRepresentation { + // Not @inlinable + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _IntegerAnyHashableBox(self)) + } +} + // Create an ambiguity when indexing or slicing // Range[OfStrideable]<${Self}> outside a generic context. See @@ -4270,6 +4260,73 @@ extension SignedInteger where Self : FixedWidthInteger { % end } +internal struct _IntegerAnyHashableBox< + Base: FixedWidthInteger +>: _AnyHashableBox { + internal let _value: Base + + internal init(_ value: Base) { + self._value = value + } + + internal var _canonicalBox: _AnyHashableBox { + // We need to follow NSNumber semantics here; the AnyHashable forms of + // integer types holding the same mathematical value should compare equal. + // Sign-extend value to a 64-bit integer. This will generate hash conflicts + // between, say -1 and UInt.max, but that's fine. + if _value < 0 { + return _IntegerAnyHashableBox(Int64(truncatingIfNeeded: _value)) + } + return _IntegerAnyHashableBox(UInt64(truncatingIfNeeded: _value)) + } + + internal func _isEqual(to box: _AnyHashableBox) -> Bool? { + if Base.self == UInt64.self { + guard let box = box as? _IntegerAnyHashableBox else { return nil } + return _value == box._value + } + if Base.self == Int64.self { + guard let box = box as? _IntegerAnyHashableBox else { return nil } + return _value == box._value + } + _preconditionFailure("self isn't canonical") + } + + internal var _hashValue: Int { + _sanityCheck(Base.self == UInt64.self || Base.self == Int64.self, + "self isn't canonical") + return _value.hashValue + } + + internal func _hash(into hasher: inout Hasher) { + _sanityCheck(Base.self == UInt64.self || Base.self == Int64.self, + "self isn't canonical") + _value.hash(into: &hasher) + } + + internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + _sanityCheck(Base.self == UInt64.self || Base.self == Int64.self, + "self isn't canonical") + return _value._rawHashValue(seed: _seed) + } + + internal var _base: Any { + return _value + } + + internal func _unbox() -> T? { + return _value as? T + } + + internal func _downCastConditional( + into result: UnsafeMutablePointer + ) -> Bool { + guard let value = _value as? T else { return false } + result.initialize(to: value) + return true + } +} + // ${'Local Variables'}: // eval: (read-only-mode 1) // End: diff --git a/stdlib/public/core/NewtypeWrapper.swift b/stdlib/public/core/NewtypeWrapper.swift index 5024c9d17d9e6..c9493a59a48ad 100644 --- a/stdlib/public/core/NewtypeWrapper.swift +++ b/stdlib/public/core/NewtypeWrapper.swift @@ -13,9 +13,10 @@ /// An implementation detail used to implement support importing /// (Objective-)C entities marked with the swift_newtype Clang /// attribute. -public protocol _SwiftNewtypeWrapper : RawRepresentable { } +public protocol _SwiftNewtypeWrapper +: RawRepresentable, _HasCustomAnyHashableRepresentation { } -extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue : Hashable { +extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue: Hashable { /// The hash value. @inlinable public var hashValue: Int { @@ -31,6 +32,70 @@ extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue : Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(rawValue) } + + @inlinable // FIXME(sil-serialize-all) + public func _rawHashValue(seed: (UInt64, UInt64)) -> Int { + return rawValue._rawHashValue(seed: seed) + } +} + +extension _SwiftNewtypeWrapper { + public func _toCustomAnyHashable() -> AnyHashable? { + return nil + } +} + +extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue: Hashable { + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _NewtypeWrapperAnyHashableBox(self)) + } +} + +internal struct _NewtypeWrapperAnyHashableBox: _AnyHashableBox +where Base: _SwiftNewtypeWrapper & Hashable, Base.RawValue: Hashable { + var _value: Base + + init(_ value: Base) { + self._value = value + } + + var _canonicalBox: _AnyHashableBox { + return (_value.rawValue as AnyHashable)._box._canonicalBox + } + + func _isEqual(to other: _AnyHashableBox) -> Bool? { + _preconditionFailure("_isEqual called on non-canonical AnyHashable box") + } + + var _hashValue: Int { + _preconditionFailure("_hashValue called on non-canonical AnyHashable box") + } + + func _hash(into hasher: inout Hasher) { + _preconditionFailure("_hash(into:) called on non-canonical AnyHashable box") + } + + func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + _preconditionFailure("_rawHashValue(_seed:) called on non-canonical AnyHashable box") + } + + var _base: Any { return _value } + + func _unbox() -> T? { + return _value as? T ?? _value.rawValue as? T + } + + func _downCastConditional(into result: UnsafeMutablePointer) -> Bool { + if let value = _value as? T { + result.initialize(to: value) + return true + } + if let value = _value.rawValue as? T { + result.initialize(to: value) + return true + } + return false + } } #if _runtime(_ObjC) diff --git a/stdlib/public/core/Set.swift b/stdlib/public/core/Set.swift index 1441f79980a2a..0730b3070b7b8 100644 --- a/stdlib/public/core/Set.swift +++ b/stdlib/public/core/Set.swift @@ -503,6 +503,61 @@ extension Set: Hashable { } } +extension Set: _HasCustomAnyHashableRepresentation { + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(_box: _SetAnyHashableBox(self)) + } +} + +internal struct _SetAnyHashableBox: _AnyHashableBox { + internal let _value: Set + internal let _canonical: Set + + internal init(_ value: Set) { + self._value = value + self._canonical = value as Set + } + + internal var _base: Any { + return _value + } + + internal var _canonicalBox: _AnyHashableBox { + return _SetAnyHashableBox(_canonical) + } + + internal func _isEqual(to other: _AnyHashableBox) -> Bool? { + guard let other = other as? _SetAnyHashableBox else { + return nil + } + return _canonical == other._value + } + + internal var _hashValue: Int { + return _canonical.hashValue + } + + internal func _hash(into hasher: inout Hasher) { + _canonical.hash(into: &hasher) + } + + func _rawHashValue(_seed: (UInt64, UInt64)) -> Int { + return _canonical._rawHashValue(seed: _seed) + } + + internal func _unbox() -> T? { + return _value as? T + } + + internal func _downCastConditional( + into result: UnsafeMutablePointer + ) -> Bool { + guard let value = _value as? T else { return false } + result.initialize(to: value) + return true + } +} + extension Set: SetAlgebra { /// Inserts the given element in the set if it is not already present. diff --git a/test/stdlib/AnyHashableCasts.swift.gyb b/test/stdlib/AnyHashableCasts.swift.gyb index 4eaaf507c427e..3a6f81fe27392 100644 --- a/test/stdlib/AnyHashableCasts.swift.gyb +++ b/test/stdlib/AnyHashableCasts.swift.gyb @@ -1,10 +1,10 @@ // RUN: %empty-directory(%t) // // RUN: %gyb %s -o %t/AnyHashableCasts.swift -// RUN: %target-build-swift -g -module-name a %t/AnyHashableCasts.swift -o %t.out -// RUN: %target-run %t.out -// RUN: %target-build-swift -g -O -module-name a %t/AnyHashableCasts.swift -o %t.out.optimized -// RUN: %target-run %t.out.optimized +// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -module-name a %t/AnyHashableCasts.swift -o %t.out +// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out +// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -O -module-name a %t/AnyHashableCasts.swift -o %t.out.optimized +// RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out.optimized // REQUIRES: executable_test import StdlibUnittest @@ -117,34 +117,129 @@ AnyHashableCasts.test("${valueExpr} as ${coercedType} as? ${castType}") { % end #if _runtime(_ObjC) +// A wrapper type around Int that bridges to NSNumber. +struct IntWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { + let rawValue: Int +} + +// A wrapper type around Int that bridges to NSNumber. +struct IntWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { + let rawValue: Int +} + +AnyHashableCasts.test("Wrappers around bridged integers") { + let wrapper1: AnyHashable = IntWrapper1(rawValue: 42) + let wrapper2: AnyHashable = IntWrapper2(rawValue: 42) + let integer: AnyHashable = 42 as Int + let byte: AnyHashable = 42 as UInt8 + let double: AnyHashable = 42.0 as Double + let number: AnyHashable = 42 as NSNumber + + // Wrappers compare equal to their wrapped value as AnyHashable. + expectEqual(wrapper1, wrapper2) + expectEqual(wrapper1, integer) + expectEqual(wrapper1, byte) + expectEqual(wrapper1, double) + expectEqual(wrapper1, number) + + // Original types are preserved in the base property. + expectTrue(wrapper1.base is IntWrapper1) + expectTrue(wrapper2.base is IntWrapper2) + expectTrue(integer.base is Int) + expectTrue(byte.base is UInt8) + expectTrue(double.base is Double) + expectTrue(number.base is NSNumber) // Through bridging + + // AnyHashable forms can be casted to any standard numeric type that can hold + // their value. + expectNotNil(wrapper1 as? IntWrapper1) + expectNotNil(wrapper1 as? IntWrapper2) + expectNotNil(wrapper1 as? Int) + expectNotNil(wrapper1 as? UInt8) + expectNotNil(wrapper1 as? Double) + expectNotNil(wrapper1 as? NSNumber) + + expectNotNil(byte as? IntWrapper1) + expectNotNil(byte as? IntWrapper2) + expectNotNil(byte as? Int) + expectNotNil(byte as? UInt8) + expectNotNil(byte as? Double) + expectNotNil(byte as? NSNumber) + + expectNotNil(integer as? IntWrapper1) + expectNotNil(integer as? IntWrapper2) + expectNotNil(integer as? Int) + expectNotNil(integer as? UInt8) + expectNotNil(integer as? Double) + expectNotNil(integer as? NSNumber) + + expectNotNil(double as? IntWrapper1) + expectNotNil(double as? IntWrapper2) + expectNotNil(double as? Int) + expectNotNil(double as? UInt8) + expectNotNil(double as? Double) + expectNotNil(double as? NSNumber) + + expectNotNil(number as? IntWrapper1) + expectNotNil(number as? IntWrapper2) + expectNotNil(number as? Int) + expectNotNil(number as? UInt8) + expectNotNil(number as? Double) + expectNotNil(number as? NSNumber) + + // We can't cast to a numeric type that can't hold the value. + let big: AnyHashable = Int32.max + expectNotNil(big as? IntWrapper1) + expectNotNil(big as? IntWrapper2) + expectNotNil(big as? Int) + expectNil(big as? UInt8) // <-- + expectNotNil(big as? Double) + expectNotNil(big as? NSNumber) +} + // A wrapper type around a String that bridges to NSString. -struct StringWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { +struct StringWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: String } // A wrapper type around a String that bridges to NSString. -struct StringWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { +struct StringWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: String } -AnyHashableCasts.test("Wrappers around bridged types") { +AnyHashableCasts.test("Wrappers around bridged strings") { let wrapper1Hello: AnyHashable = StringWrapper1(rawValue: "hello") + let wrapper2Hello: AnyHashable = StringWrapper2(rawValue: "hello") let stringHello: AnyHashable = "hello" as String let nsStringHello: AnyHashable = "hello" as NSString - // Casting from Swift wrapper maintains type identity + // Wrappers compare equal to their wrapped value as AnyHashable. + expectEqual(wrapper1Hello, wrapper2Hello) + expectEqual(wrapper1Hello, stringHello) + expectEqual(wrapper1Hello, nsStringHello) + expectEqual(wrapper2Hello, stringHello) + expectEqual(wrapper2Hello, nsStringHello) + expectEqual(stringHello, nsStringHello) + + // Type identity is maintained through the base property. + expectTrue(wrapper1Hello.base is StringWrapper1) + expectTrue(wrapper2Hello.base is StringWrapper2) + expectTrue(stringHello.base is String) + expectTrue(nsStringHello.base is NSString) // Through bridging + + // Swift wrapper's AnyHashable form doesn't enfore type identity. expectNotNil(wrapper1Hello as? StringWrapper1) - expectNil(wrapper1Hello as? StringWrapper2) - expectNil(wrapper1Hello as? String) + expectNotNil(wrapper1Hello as? StringWrapper2) + expectNotNil(wrapper1Hello as? String) expectNotNil(wrapper1Hello as? NSString) - // Casting from String maintains type identity - expectNil(stringHello as? StringWrapper1) - expectNil(stringHello as? StringWrapper2) + // String's AnyHashable form doesn't enfore type identity. + expectNotNil(stringHello as? StringWrapper1) + expectNotNil(stringHello as? StringWrapper2) expectNotNil(stringHello as? String) expectNotNil(stringHello as? NSString) - // Casting form NSString works with anything. + // NSString's AnyHashable form doesn't enfore type identity. expectNotNil(nsStringHello as? StringWrapper1) expectNotNil(nsStringHello as? StringWrapper2) expectNotNil(nsStringHello as? String) diff --git a/validation-test/stdlib/AnyHashable.swift.gyb b/validation-test/stdlib/AnyHashable.swift.gyb index 05084ad4bdd8e..c6fa6cfd2650d 100644 --- a/validation-test/stdlib/AnyHashable.swift.gyb +++ b/validation-test/stdlib/AnyHashable.swift.gyb @@ -767,6 +767,132 @@ AnyHashableTests.test("AnyHashable(MinimalHashableRCSwiftError).base") { 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 { @@ -778,126 +904,101 @@ struct StringWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { let rawValue: String } -AnyHashableTests.test("AnyHashable(Wrappers)/Hashable") { - let values: [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, +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, + ] ] - - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - // Elements in [0, 3] match 3. - if lhs == 3 { return rhs >= 0 && rhs <= 3 } - if rhs == 3 { return lhs >= 0 && lhs <= 3 } - - // Elements in [4, 7] match 7. - if lhs == 7 { return rhs >= 4 && rhs <= 7 } - if rhs == 7 { return lhs >= 4 && lhs <= 7 } - - return lhs == rhs - } - - func hashEqualityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - // Elements in [0, 3] hash the same, as do elements in [4, 7]. - return lhs / 4 == rhs / 4 - } - - checkHashable( - values, - equalityOracle: equalityOracle, - hashEqualityOracle: hashEqualityOracle, - allowBrokenTransitivity: true) + checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Set)/Hashable") { - let values: [AnyHashable] = [ - Set([1, 2, 3]), - 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])]), + 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])]), + ], ] - - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - switch (lhs, rhs) { - case (0...1, 0...1): return true - case (2...3, 2...3): return true - case (4...5, 4...5): return true - case (6...7, 6...7): return true - default: return false - } - } - - checkHashable( - values, - equalityOracle: equalityOracle, - allowBrokenTransitivity: true) + checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Array)/Hashable") { - let values: [AnyHashable] = [ - [1, 2, 3], - NSArray(array: [1, 2, 3]), - [3, 2, 1], - 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])]), + 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])]), + ] ] - - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - switch (lhs, rhs) { - case (0...1, 0...1): return true - case (2...3, 2...3): return true - case (4...5, 4...5): return true - case (6...7, 6...7): return true - default: return false - } - } - - checkHashable(values, equalityOracle: equalityOracle, - allowBrokenTransitivity: true) + checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(Dictionary)/Hashable") { - let values: [AnyHashable] = [ - ["hello": 1, "world": 2], - 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]]), + 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]]), + ], ] - - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - switch (lhs, rhs) { - case (0...1, 0...1): return true - case (2...3, 2...3): return true - case (4...5, 4...5): return true - case (6...7, 6...7): return true - default: return false - } - } - - checkHashable(values, equalityOracle: equalityOracle, - allowBrokenTransitivity: true) + checkHashableGroups(groups) } AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError))/Hashable") { diff --git a/validation-test/stdlib/DictionaryAnyHashableExtensions.swift b/validation-test/stdlib/DictionaryAnyHashableExtensions.swift index 8cfaaa6030df2..9d577945f9eba 100644 --- a/validation-test/stdlib/DictionaryAnyHashableExtensions.swift +++ b/validation-test/stdlib/DictionaryAnyHashableExtensions.swift @@ -6,26 +6,73 @@ import StdlibUnittest var DictionaryTests = TestSuite("Dictionary") DictionaryTests.test("index(forKey:)") { - let d: [AnyHashable : Int] = [ - AnyHashable(10) : 1010, - AnyHashable(20) : 2020, - AnyHashable(30.0) : 3030, + let a = AnyHashable(10 as UInt16) + let b = AnyHashable(20) + let c = AnyHashable(30.0) + let d: [AnyHashable: Int] = [ + a: 1010, + b: 2020, + c: 3030, ] - expectEqual(1010, d[d.index(forKey: 10)!].value) - expectEqual(2020, d[d.index(forKey: 20)!].value) - expectEqual(3030, d[d.index(forKey: 30.0)!].value) - - expectNil(d.index(forKey: 10.0)) - expectNil(d.index(forKey: 20.0)) - expectNil(d.index(forKey: 30)) + for (key, k, value) in [(a, 10, 1010), (b, 20, 2020), (c, 30, 3030)] { + let index = d.index(forKey: key)! + expectEqual(value, d[index].value) + // We must be able to look up the same number in any representation. + expectEqual(index, d.index(forKey: UInt8(k))) + expectEqual(index, d.index(forKey: UInt16(k))) + expectEqual(index, d.index(forKey: UInt32(k))) + expectEqual(index, d.index(forKey: UInt64(k))) + expectEqual(index, d.index(forKey: UInt(k))) + expectEqual(index, d.index(forKey: Int8(k))) + expectEqual(index, d.index(forKey: Int16(k))) + expectEqual(index, d.index(forKey: Int32(k))) + expectEqual(index, d.index(forKey: Int64(k))) + expectEqual(index, d.index(forKey: Int(k))) + expectEqual(index, d.index(forKey: Float(k))) + expectEqual(index, d.index(forKey: Double(k))) + + expectNil(d.index(forKey: String(k))) + } } DictionaryTests.test("subscript(_:)") { - var d: [AnyHashable : Int] = [ - AnyHashable(10) : 1010, - AnyHashable(20) : 2020, - AnyHashable(30.0) : 3030, + let a = AnyHashable(10 as UInt16) + let b = AnyHashable(20) + let c = AnyHashable(30.0) + let d: [AnyHashable: Int] = [ + a: 1010, + b: 2020, + c: 3030, + ] + + for (key, k, value) in [(a, 10, 1010), (b, 20, 2020), (c, 30, 3030)] { + let index = d.index(forKey: key)! + expectEqual(value, d[key]) + // We must be able to look up the same number in any representation. + expectEqual(value, d[UInt8(k)]) + expectEqual(value, d[UInt16(k)]) + expectEqual(value, d[UInt32(k)]) + expectEqual(value, d[UInt64(k)]) + expectEqual(value, d[UInt(k)]) + expectEqual(value, d[Int8(k)]) + expectEqual(value, d[Int16(k)]) + expectEqual(value, d[Int32(k)]) + expectEqual(value, d[Int64(k)]) + expectEqual(value, d[Int(k)]) + expectEqual(value, d[Float(k)]) + expectEqual(value, d[Double(k)]) + + expectNil(d[String(k)]) + } +} + + +DictionaryTests.test("subscript(_:)/2") { + var d: [AnyHashable: Int] = [ + AnyHashable(10): 1010, + AnyHashable(20): 2020, + AnyHashable(30.0): 3030, ] expectEqual(1010, d[10]) @@ -100,57 +147,61 @@ DictionaryTests.test("updateValue(_:forKey:)") { expectEqual(expected, d) } - expectNil(d.updateValue(4040, forKey: 10.0)) + expectEqual(101010, d.updateValue(4040, forKey: 10.0)) do { let expected: [AnyHashable : Int] = [ - AnyHashable(10) : 101010, + AnyHashable(10) : 4040, AnyHashable(20) : 202020, AnyHashable(30.0) : 303030, - AnyHashable(10.0) : 4040, ] expectEqual(expected, d) } - expectNil(d.updateValue(5050, forKey: 20.0)) + expectEqual(202020, d.updateValue(5050, forKey: 20.0)) do { let expected: [AnyHashable : Int] = [ - AnyHashable(10) : 101010, - AnyHashable(20) : 202020, + AnyHashable(10) : 4040, + AnyHashable(20) : 5050, AnyHashable(30.0) : 303030, - AnyHashable(10.0) : 4040, - AnyHashable(20.0) : 5050, ] expectEqual(expected, d) } - expectNil(d.updateValue(6060, forKey: 30)) + expectEqual(303030, d.updateValue(6060, forKey: 30)) do { let expected: [AnyHashable : Int] = [ - AnyHashable(10) : 101010, - AnyHashable(20) : 202020, - AnyHashable(30.0) : 303030, - AnyHashable(10.0) : 4040, - AnyHashable(20.0) : 5050, - AnyHashable(30) : 6060, + AnyHashable(10) : 4040, + AnyHashable(20) : 5050, + AnyHashable(30.0) : 6060, ] expectEqual(expected, d) } } DictionaryTests.test("removeValue(forKey:)") { - var d: [AnyHashable : Int] = [ - AnyHashable(10) : 1010, + let d: [AnyHashable : Int] = [ + AnyHashable(10 as UInt8) : 1010, AnyHashable(20) : 2020, AnyHashable(30.0) : 3030, ] - expectNil(d.removeValue(forKey: 10.0)) - expectNil(d.removeValue(forKey: 20.0)) - expectNil(d.removeValue(forKey: 30)) - - expectEqual(1010, d.removeValue(forKey: 10)!) - expectEqual(2020, d.removeValue(forKey: 20)!) - expectEqual(3030, d.removeValue(forKey: 30.0)!) + for (key, value) in [(10, 1010), (20, 2020), (30, 3030)] { + var dd = d + expectEqual(value, dd.removeValue(forKey: UInt8(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: UInt16(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: UInt32(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: UInt64(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: UInt(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Int8(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Int16(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Int32(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Int64(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Int(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Float(key))) + dd = d; expectEqual(value, dd.removeValue(forKey: Double(key))) + + dd = d; expectNil(dd.removeValue(forKey: String(key))) + } } runAllTests() diff --git a/validation-test/stdlib/FixedPoint.swift.gyb b/validation-test/stdlib/FixedPoint.swift.gyb index 7ac2f9d2a5ce1..dd371bfe74d04 100644 --- a/validation-test/stdlib/FixedPoint.swift.gyb +++ b/validation-test/stdlib/FixedPoint.swift.gyb @@ -233,22 +233,26 @@ hash_value_test_template = gyb.parse_template("hash_value", % for self_ty in all_integer_types(word_bits): % Self = self_ty.stdlib_name -FixedPoint.test("${Self}.hashValue") { +FixedPoint.test("${Self}.hash(into:)") { % for bit_pattern in test_bit_patterns: do { % input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed) let input = get${Self}(${input}) - let output = getInt(input.hashValue) + var hasher = Hasher() + input.hash(into: &hasher) + let output = getInt(hasher.finalize()) - var hasher = _SipHash13(_seed: Hasher._seed) -% if prepare_bit_pattern(input, word_bits, self_ty.is_signed) == input: - hasher._combine(UInt(truncatingIfNeeded: ${input} as ${"" if self_ty.is_signed else "U"}Int)) +% reference = prepare_bit_pattern(bit_pattern, self_ty.bits, False) +% if self_ty.bits == 64: + let expected = Hasher._hash(seed: Hasher._seed, ${reference} as UInt64) % else: - hasher._combine(UInt64(truncatingIfNeeded: input)) + let expected = Hasher._hash( + seed: Hasher._seed, + bytes: ${reference}, + count: ${self_ty.bits / 8}) % end - let expected = getInt(Int(truncatingIfNeeded: hasher.finalize())) expectEqual(expected, output, "input: \(input)") } diff --git a/validation-test/stdlib/SetAnyHashableExtensions.swift b/validation-test/stdlib/SetAnyHashableExtensions.swift index 3ab8cea2682f9..54ef1f72f2d20 100644 --- a/validation-test/stdlib/SetAnyHashableExtensions.swift +++ b/validation-test/stdlib/SetAnyHashableExtensions.swift @@ -28,28 +28,45 @@ var SetTests = TestSuite("Set") SetTests.test("contains(_:)") { let s: Set = [ - AnyHashable(1010), AnyHashable(2020), AnyHashable(3030.0) + AnyHashable(1010 as UInt16), AnyHashable(2020), AnyHashable(3030.0) ] - expectTrue(s.contains(1010)) - expectTrue(s.contains(2020)) - expectTrue(s.contains(3030.0)) - - expectFalse(s.contains(1010.0)) - expectFalse(s.contains(2020.0)) - expectFalse(s.contains(3030)) + for i in [1010, 2020, 3030] { + // We must be able to look up the same number in any representation. + expectTrue(s.contains(UInt16(i))) + expectTrue(s.contains(UInt32(i))) + expectTrue(s.contains(UInt64(i))) + expectTrue(s.contains(UInt(i))) + expectTrue(s.contains(Int16(i))) + expectTrue(s.contains(Int32(i))) + expectTrue(s.contains(Int64(i))) + expectTrue(s.contains(Int(i))) + expectTrue(s.contains(Float(i))) + expectTrue(s.contains(Double(i))) + + expectFalse(s.contains(String(i))) + } } SetTests.test("index(of:)") { - let s: Set = [ - AnyHashable(1010), AnyHashable(2020), AnyHashable(3030.0) - ] - expectEqual(AnyHashable(1010), s[s.firstIndex(of: 1010)!]) - expectEqual(AnyHashable(2020), s[s.firstIndex(of: 2020)!]) - expectEqual(AnyHashable(3030.0), s[s.firstIndex(of: 3030.0)!]) - - expectNil(s.firstIndex(of: 1010.0)) - expectNil(s.firstIndex(of: 2020.0)) - expectNil(s.firstIndex(of: 3030)) + let a = AnyHashable(1010 as UInt16) + let b = AnyHashable(2020) + let c = AnyHashable(3030.0) + let s: Set = [a, b, c] + for (element, i) in [(a, 1010), (b, 2020), (c, 3030)] { + let index = s.firstIndex(of: element)! + + // We must be able to look up the same number in any representation. + expectEqual(index, s.firstIndex(of: UInt16(i))) + expectEqual(index, s.firstIndex(of: UInt32(i))) + expectEqual(index, s.firstIndex(of: UInt64(i))) + expectEqual(index, s.firstIndex(of: UInt(i))) + expectEqual(index, s.firstIndex(of: Int16(i))) + expectEqual(index, s.firstIndex(of: Int32(i))) + expectEqual(index, s.firstIndex(of: Int64(i))) + expectEqual(index, s.firstIndex(of: Int(i))) + expectEqual(index, s.firstIndex(of: Float(i))) + expectEqual(index, s.firstIndex(of: Double(i))) + } } SetTests.test("insert(_:)") { @@ -230,5 +247,28 @@ SetTests.test("remove(_:)/CastTrap") s.remove(TestHashableDerivedB(2020, identity: 2)) } +SetTests.test("Hashable/Conversions") { + let input: [Set] = [ + [10 as UInt8, 20 as UInt8, 30 as UInt8], + [10 as UInt16, 20 as UInt16, 30 as UInt16], + [10 as UInt32, 20 as UInt32, 30 as UInt32], + [10 as UInt64, 20 as UInt64, 30 as UInt64], + [10 as UInt, 20 as UInt, 30 as UInt], + [10 as Int8, 20 as Int8, 30 as Int8], + [10 as Int16, 20 as Int16, 30 as Int16], + [10 as Int32, 20 as Int32, 30 as Int32], + [10 as Int64, 20 as Int64, 30 as Int64], + [10 as Int, 20 as Int, 30 as Int], + [10 as Float, 20 as Float, 30 as Float], + [10 as Double, 20 as Double, 30 as Double], + [[1, 2, 3] as Set, [2, 3, 4] as Set, [3, 4, 5] as Set], + [[1, 2, 3] as Set, [2, 3, 4] as Set, [3, 4, 5] as Set], + [[1, 2, 3] as Set, [2, 3, 4] as Set, [3, 4, 5] as Set], + ] + + checkHashable(input, equalityOracle: { ($0 < 12) == ($1 < 12) }) +} + + runAllTests()