diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift b/stdlib/private/StdlibUnittest/StdlibUnittest.swift index 5b9e637979cb6..dc698cfe1cd6d 100644 --- a/stdlib/private/StdlibUnittest/StdlibUnittest.swift +++ b/stdlib/private/StdlibUnittest/StdlibUnittest.swift @@ -2346,17 +2346,22 @@ internal func _checkEquatableImpl( let isEqualXY = x == y expectEqual( predictedXY, isEqualXY, - (predictedXY - ? "expected equal, found not equal\n" - : "expected not equal, found equal\n") + - "lhs (at index \(i)): \(String(reflecting: x))\n" + - "rhs (at index \(j)): \(String(reflecting: y))", + """ + \((predictedXY + ? "expected equal, found not equal" + : "expected not equal, found equal")) + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, stackTrace: stackTrace.pushIf(showFrame, file: file, line: line)) // Not-equal is an inverse of equal. expectNotEqual( isEqualXY, x != y, - "lhs (at index \(i)): \(String(reflecting: x))\nrhs (at index \(j)): \(String(reflecting: y))", + """ + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, stackTrace: stackTrace.pushIf(showFrame, file: file, line: line)) if !allowBrokenTransitivity { @@ -2398,6 +2403,10 @@ public func checkEquatable( showFrame: false) } +/// Produce an integer hash value for `value` by feeding it to a dedicated +/// `Hasher`. This is always done by calling the `hash(into:)` method. +/// If a non-nil `seed` is given, it is used to perturb the hasher state; +/// this is useful for resolving accidental hash collisions. internal func hash(_ value: H, seed: Int? = nil) -> Int { var hasher = Hasher() if let seed = seed { @@ -2413,6 +2422,7 @@ internal func hash(_ value: H, seed: Int? = nil) -> Int { public func checkHashableGroups( _ groups: Groups, _ message: @autoclosure () -> String = "", + allowIncompleteHashing: Bool = false, stackTrace: SourceLocStack = SourceLocStack(), showFrame: Bool = true, file: String = #file, line: UInt = #line @@ -2430,6 +2440,7 @@ public func checkHashableGroups( equalityOracle: equalityOracle, hashEqualityOracle: equalityOracle, allowBrokenTransitivity: false, + allowIncompleteHashing: allowIncompleteHashing, stackTrace: stackTrace.pushIf(showFrame, file: file, line: line), showFrame: false) } @@ -2441,6 +2452,7 @@ public func checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, allowBrokenTransitivity: Bool = false, + allowIncompleteHashing: Bool = false, _ message: @autoclosure () -> String = "", stackTrace: SourceLocStack = SourceLocStack(), showFrame: Bool = true, @@ -2451,6 +2463,7 @@ public func checkHashable( equalityOracle: equalityOracle, hashEqualityOracle: equalityOracle, allowBrokenTransitivity: allowBrokenTransitivity, + allowIncompleteHashing: allowIncompleteHashing, stackTrace: stackTrace.pushIf(showFrame, file: file, line: line), showFrame: false) } @@ -2464,6 +2477,7 @@ public func checkHashable( equalityOracle: (Instances.Index, Instances.Index) -> Bool, hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, allowBrokenTransitivity: Bool = false, + allowIncompleteHashing: Bool = false, _ message: @autoclosure () -> String = "", stackTrace: SourceLocStack = SourceLocStack(), showFrame: Bool = true, @@ -2516,12 +2530,12 @@ public func checkHashable( expectEqual( x._rawHashValue(seed: 0), y._rawHashValue(seed: 0), """ - _rawHashValue expected to match, found to differ + _rawHashValue(seed:) expected to match, found to differ lhs (at index \(i)): \(x) rhs (at index \(j)): \(y) """, stackTrace: stackTrace.pushIf(showFrame, file: file, line: line)) - } else { + } else if !allowIncompleteHashing { // Try a few different seeds; at least one of them should discriminate // between the hashes. It is extremely unlikely this check will fail // all ten attempts, unless the type's hash encoding is not unique, diff --git a/stdlib/public/Darwin/Foundation/AffineTransform.swift b/stdlib/public/Darwin/Foundation/AffineTransform.swift index 7978bd90a8db0..9df28832ee5ac 100644 --- a/stdlib/public/Darwin/Foundation/AffineTransform.swift +++ b/stdlib/public/Darwin/Foundation/AffineTransform.swift @@ -276,8 +276,13 @@ public struct AffineTransform : ReferenceConvertible, Hashable, CustomStringConv return newSize } - public var hashValue : Int { - return Int(m11 + m12 + m21 + m22 + tX + tY) + public func hash(into hasher: inout Hasher) { + hasher.combine(m11) + hasher.combine(m12) + hasher.combine(m21) + hasher.combine(m22) + hasher.combine(tX) + hasher.combine(tY) } public var description: String { diff --git a/stdlib/public/Darwin/Foundation/Calendar.swift b/stdlib/public/Darwin/Foundation/Calendar.swift index 2d343e3bd2ec4..8ea8159080099 100644 --- a/stdlib/public/Darwin/Foundation/Calendar.swift +++ b/stdlib/public/Darwin/Foundation/Calendar.swift @@ -899,15 +899,16 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi // MARK: - - public var hashValue : Int { - // We implement hash ourselves, because we need to make sure autoupdating calendars have the same hash + public func hash(into hasher: inout Hasher) { + // We need to make sure autoupdating calendars have the same hash if _autoupdating { - return 1 + hasher.combine(false) } else { - return _handle.map { $0.hash } + hasher.combine(true) + hasher.combine(_handle.map { $0 }) } } - + // MARK: - // MARK: Conversion Functions diff --git a/stdlib/public/Darwin/Foundation/CharacterSet.swift b/stdlib/public/Darwin/Foundation/CharacterSet.swift index 68df36ea75cf4..79770326e2f49 100644 --- a/stdlib/public/Darwin/Foundation/CharacterSet.swift +++ b/stdlib/public/Darwin/Foundation/CharacterSet.swift @@ -52,15 +52,15 @@ fileprivate final class __CharacterSetStorage : Hashable { // MARK: - - fileprivate var hashValue : Int { + fileprivate func hash(into hasher: inout Hasher) { switch _backing { case .immutable(let cs): - return Int(CFHash(cs)) + hasher.combine(CFHash(cs)) case .mutable(let cs): - return Int(CFHash(cs)) + hasher.combine(CFHash(cs)) } } - + fileprivate static func ==(lhs : __CharacterSetStorage, rhs : __CharacterSetStorage) -> Bool { switch (lhs._backing, rhs._backing) { case (.immutable(let cs1), .immutable(let cs2)): @@ -754,8 +754,8 @@ public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgeb // MARK: - - public var hashValue: Int { - return _storage.hashValue + public func hash(into hasher: inout Hasher) { + hasher.combine(_storage) } /// Returns true if the two `CharacterSet`s are equal. diff --git a/stdlib/public/Darwin/Foundation/Date.swift b/stdlib/public/Darwin/Foundation/Date.swift index fcb455c39ca54..39bf4c32024ca 100644 --- a/stdlib/public/Darwin/Foundation/Date.swift +++ b/stdlib/public/Darwin/Foundation/Date.swift @@ -142,14 +142,10 @@ public struct Date : ReferenceConvertible, Comparable, Equatable { */ public static let distantPast = Date(timeIntervalSinceReferenceDate: -63114076800.0) - public var hashValue: Int { - if #available(macOS 10.12, iOS 10.0, *) { - return Int(bitPattern: __CFHashDouble(_time)) - } else { // 10.11 and previous behavior fallback; this must allocate a date to reference the hash value and then throw away the reference - return NSDate(timeIntervalSinceReferenceDate: _time).hash - } + public func hash(into hasher: inout Hasher) { + hasher.combine(_time) } - + /// Compare two `Date` values. public func compare(_ other: Date) -> ComparisonResult { if _time < other.timeIntervalSinceReferenceDate { diff --git a/stdlib/public/Darwin/Foundation/DateComponents.swift b/stdlib/public/Darwin/Foundation/DateComponents.swift index f31e8400b46be..01c711db1c73f 100644 --- a/stdlib/public/Darwin/Foundation/DateComponents.swift +++ b/stdlib/public/Darwin/Foundation/DateComponents.swift @@ -263,10 +263,10 @@ public struct DateComponents : ReferenceConvertible, Hashable, Equatable, _Mutab // MARK: - - public var hashValue : Int { - return _handle.map { $0.hash } + public func hash(into hasher: inout Hasher) { + hasher.combine(_handle._uncopiedReference()) } - + // MARK: - Bridging Helpers fileprivate init(reference: __shared NSDateComponents) { diff --git a/stdlib/public/Darwin/Foundation/DateInterval.swift b/stdlib/public/Darwin/Foundation/DateInterval.swift index 5c58a063e345d..6ee7f4afe01fc 100644 --- a/stdlib/public/Darwin/Foundation/DateInterval.swift +++ b/stdlib/public/Darwin/Foundation/DateInterval.swift @@ -155,15 +155,11 @@ public struct DateInterval : ReferenceConvertible, Comparable, Hashable, Codable return false } - public var hashValue: Int { - var buf: (UInt, UInt) = (UInt(start.timeIntervalSinceReferenceDate), UInt(end.timeIntervalSinceReferenceDate)) - return withUnsafeMutablePointer(to: &buf) { - $0.withMemoryRebound(to: UInt8.self, capacity: 2 * MemoryLayout.size / MemoryLayout.size) { - return Int(bitPattern: CFHashBytes($0, CFIndex(MemoryLayout.size * 2))) - } - } + public func hash(into hasher: inout Hasher) { + hasher.combine(start) + hasher.combine(duration) } - + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public static func ==(lhs: DateInterval, rhs: DateInterval) -> Bool { return lhs.start == rhs.start && lhs.duration == rhs.duration diff --git a/stdlib/public/Darwin/Foundation/Decimal.swift b/stdlib/public/Darwin/Foundation/Decimal.swift index 00d958ad046cd..a747d94a2d5e0 100644 --- a/stdlib/public/Darwin/Foundation/Decimal.swift +++ b/stdlib/public/Darwin/Foundation/Decimal.swift @@ -177,8 +177,12 @@ extension Decimal : Hashable, Comparable { return _isNegative != 0 ? -d : d } - public var hashValue: Int { - return Int(bitPattern: __CFHashDouble(doubleValue)) + public func hash(into hasher: inout Hasher) { + // FIXME: This is a weak hash. We should rather normalize self to a + // canonical member of the exact same equivalence relation that + // NSDecimalCompare implements, then simply feed all components to the + // hasher. + hasher.combine(doubleValue) } public static func ==(lhs: Decimal, rhs: Decimal) -> Bool { diff --git a/stdlib/public/Darwin/Foundation/IndexPath.swift b/stdlib/public/Darwin/Foundation/IndexPath.swift index bd293c9848f96..938cf1a61ae63 100644 --- a/stdlib/public/Darwin/Foundation/IndexPath.swift +++ b/stdlib/public/Darwin/Foundation/IndexPath.swift @@ -662,25 +662,31 @@ public struct IndexPath : ReferenceConvertible, Equatable, Hashable, MutableColl return .orderedSame } - public var hashValue: Int { - func hashIndexes(first: Int, last: Int, count: Int) -> Int { - let totalBits = MemoryLayout.size * 8 - let lengthBits = 8 - let firstIndexBits = (totalBits - lengthBits) / 2 - return count &+ (first << lengthBits) &+ (last << (lengthBits + firstIndexBits)) - } - + public func hash(into hasher: inout Hasher) { + // Note: We compare all indices in ==, so for proper hashing, we must + // also feed them all to the hasher. + // + // To ensure we have unique hash encodings in nested hashing contexts, + // we combine the count of indices as well as the indices themselves. + // (This matches what Array does.) switch _indexes { - case .empty: return 0 - case .single(let index): return index.hashValue - case .pair(let first, let second): - return hashIndexes(first: first, last: second, count: 2) - default: - let cnt = _indexes.count - return hashIndexes(first: _indexes[0], last: _indexes[cnt - 1], count: cnt) + case .empty: + hasher.combine(0) + case let .single(index): + hasher.combine(1) + hasher.combine(index) + case let .pair(first, second): + hasher.combine(2) + hasher.combine(first) + hasher.combine(second) + case let .array(indexes): + hasher.combine(indexes.count) + for index in indexes { + hasher.combine(index) + } } } - + // MARK: - Bridging Helpers fileprivate init(nsIndexPath: __shared ReferenceType) { diff --git a/stdlib/public/Darwin/Foundation/IndexSet.swift b/stdlib/public/Darwin/Foundation/IndexSet.swift index 6f019a23e2bd2..157e9bfaf8218 100644 --- a/stdlib/public/Darwin/Foundation/IndexSet.swift +++ b/stdlib/public/Darwin/Foundation/IndexSet.swift @@ -137,8 +137,8 @@ public struct IndexSet : ReferenceConvertible, Equatable, BidirectionalCollectio _handle = _MutablePairHandle(NSIndexSet(), copying: false) } - public var hashValue: Int { - return _handle.map { $0.hash } + public func hash(into hasher: inout Hasher) { + _handle.map { hasher.combine($0) } } /// Returns the number of integers in `self`. diff --git a/stdlib/public/Darwin/Foundation/Locale.swift b/stdlib/public/Darwin/Foundation/Locale.swift index ae2ad027e3f1f..0fa46ef12ba49 100644 --- a/stdlib/public/Darwin/Foundation/Locale.swift +++ b/stdlib/public/Darwin/Foundation/Locale.swift @@ -405,11 +405,12 @@ public struct Locale : Hashable, Equatable, ReferenceConvertible { // MARK: - // - public var hashValue : Int { + public func hash(into hasher: inout Hasher) { if _autoupdating { - return 1 + hasher.combine(false) } else { - return _wrapped.hash + hasher.combine(true) + hasher.combine(_wrapped) } } diff --git a/stdlib/public/Darwin/Foundation/Measurement.swift b/stdlib/public/Darwin/Foundation/Measurement.swift index d2cb56eea240a..2ca488747b131 100644 --- a/stdlib/public/Darwin/Foundation/Measurement.swift +++ b/stdlib/public/Darwin/Foundation/Measurement.swift @@ -36,8 +36,19 @@ public struct Measurement : ReferenceConvertible, Comparable, E self.unit = unit } - public var hashValue: Int { - return Int(bitPattern: __CFHashDouble(value)) + public func hash(into hasher: inout Hasher) { + // Warning: The canonicalization performed here needs to be kept in + // perfect sync with the definition of == below. The floating point + // values that are compared there must match exactly with the values fed + // to the hasher here, or hashing would break. + if let dimension = unit as? Dimension { + // We don't need to feed the base unit to the hasher here; all + // dimensional measurements of the same type share the same unit. + hasher.combine(dimension.converter.baseUnitValue(fromValue: value)) + } else { + hasher.combine(unit) + hasher.combine(value) + } } } @@ -170,6 +181,10 @@ extension Measurement { /// If `lhs.unit == rhs.unit`, returns `lhs.value == rhs.value`. Otherwise, converts `rhs` to the same unit as `lhs` and then compares the resulting values. /// - returns: `true` if the measurements are equal. public static func ==(lhs: Measurement, rhs: Measurement) -> Bool { + // Warning: This defines an equivalence relation that needs to be kept + // in perfect sync with the hash(into:) definition above. The floating + // point values that are fed to the hasher there must match exactly with + // the values compared here, or hashing would break. if lhs.unit == rhs.unit { return lhs.value == rhs.value } else { diff --git a/stdlib/public/Darwin/Foundation/NSRange.swift b/stdlib/public/Darwin/Foundation/NSRange.swift index 39d3d26243c7e..0167e9fd7a0a6 100644 --- a/stdlib/public/Darwin/Foundation/NSRange.swift +++ b/stdlib/public/Darwin/Foundation/NSRange.swift @@ -13,12 +13,9 @@ @_exported import Foundation // Clang module extension NSRange : Hashable { - public var hashValue: Int { -#if arch(i386) || arch(arm) - return Int(bitPattern: (UInt(bitPattern: location) | (UInt(bitPattern: length) << 16))) -#elseif arch(x86_64) || arch(arm64) - return Int(bitPattern: (UInt(bitPattern: location) | (UInt(bitPattern: length) << 32))) -#endif + public func hash(into hasher: inout Hasher) { + hasher.combine(location) + hasher.combine(length) } public static func==(lhs: NSRange, rhs: NSRange) -> Bool { @@ -230,4 +227,4 @@ extension NSRange : Codable { try container.encode(self.location) try container.encode(self.length) } -} \ No newline at end of file +} diff --git a/stdlib/public/Darwin/Foundation/NSStringEncodings.swift b/stdlib/public/Darwin/Foundation/NSStringEncodings.swift index 96f4356591c68..a648321edf2e5 100644 --- a/stdlib/public/Darwin/Foundation/NSStringEncodings.swift +++ b/stdlib/public/Darwin/Foundation/NSStringEncodings.swift @@ -51,8 +51,8 @@ extension String { } extension String.Encoding : Hashable { - public var hashValue : Int { - return rawValue.hashValue + public func hash(into hasher: inout Hasher) { + hasher.combine(rawValue) } public static func ==(lhs: String.Encoding, rhs: String.Encoding) -> Bool { diff --git a/stdlib/public/Darwin/Foundation/Notification.swift b/stdlib/public/Darwin/Foundation/Notification.swift index 28331c040fab1..729b59a1d75f8 100644 --- a/stdlib/public/Darwin/Foundation/Notification.swift +++ b/stdlib/public/Darwin/Foundation/Notification.swift @@ -39,10 +39,32 @@ public struct Notification : ReferenceConvertible, Equatable, Hashable { self.userInfo = userInfo } - public var hashValue: Int { - return name.rawValue.hash + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + + // FIXME: We should feed `object` to the hasher, too. However, this + // cannot be safely done if its value happens not to be Hashable. + + // FIXME: == compares `userInfo` by bridging it to NSDictionary, so its + // contents should be fully hashed here. However, to prevent stability + // issues with userInfo dictionaries that include non-Hashable values, + // we only feed the keys to the hasher here. + // + // FIXME: This makes for weak hashes. We really should hash everything + // that's compared in ==. + // + // The algorithm below is the same as the one used by Dictionary. + var commutativeKeyHash = 0 + if let userInfo = userInfo { + for (k, _) in userInfo { + var nestedHasher = hasher + nestedHasher.combine(k) + commutativeKeyHash ^= nestedHasher.finalize() + } + } + hasher.combine(commutativeKeyHash) } - + public var description: String { return "name = \(name.rawValue), object = \(String(describing: object)), userInfo = \(String(describing: userInfo))" } @@ -61,6 +83,10 @@ public struct Notification : ReferenceConvertible, Equatable, Hashable { } if let lhsObj = lhs.object { if let rhsObj = rhs.object { + // FIXME: Converting an arbitrary value to AnyObject won't use a + // stable address when the value needs to be boxed. Therefore, + // this comparison makes == non-reflexive, violating Equatable + // requirements. (rdar://problem/49797185) if lhsObj as AnyObject !== rhsObj as AnyObject { return false } @@ -73,6 +99,9 @@ public struct Notification : ReferenceConvertible, Equatable, Hashable { if lhs.userInfo != nil { if rhs.userInfo != nil { // user info must be compared in the object form since the userInfo in swift is not comparable + + // FIXME: This violates reflexivity when userInfo contains any + // non-Hashable values, for the same reason as described above. return lhs._bridgeToObjectiveC() == rhs._bridgeToObjectiveC() } else { return false diff --git a/stdlib/public/Darwin/Foundation/PersonNameComponents.swift b/stdlib/public/Darwin/Foundation/PersonNameComponents.swift index 5c8ead8ca879c..1424ab326c285 100644 --- a/stdlib/public/Darwin/Foundation/PersonNameComponents.swift +++ b/stdlib/public/Darwin/Foundation/PersonNameComponents.swift @@ -71,10 +71,10 @@ public struct PersonNameComponents : ReferenceConvertible, Hashable, Equatable, set { _applyMutation { $0.phoneticRepresentation = newValue } } } - public var hashValue : Int { - return _handle.map { $0.hash } + public func hash(into hasher: inout Hasher) { + hasher.combine(_handle._uncopiedReference()) } - + @available(macOS 10.11, iOS 9.0, *) public static func ==(lhs : PersonNameComponents, rhs: PersonNameComponents) -> Bool { // Don't copy references here; no one should be storing anything diff --git a/stdlib/public/Darwin/Foundation/TimeZone.swift b/stdlib/public/Darwin/Foundation/TimeZone.swift index 5facbe11db9e0..b335199c369e7 100644 --- a/stdlib/public/Darwin/Foundation/TimeZone.swift +++ b/stdlib/public/Darwin/Foundation/TimeZone.swift @@ -196,11 +196,12 @@ public struct TimeZone : Hashable, Equatable, ReferenceConvertible { // MARK: - - public var hashValue : Int { + public func hash(into hasher: inout Hasher) { if _autoupdating { - return 1 + hasher.combine(false) } else { - return _wrapped.hash + hasher.combine(true) + hasher.combine(_wrapped) } } diff --git a/stdlib/public/Darwin/Foundation/URL.swift b/stdlib/public/Darwin/Foundation/URL.swift index fcd48bbcdcf1c..19cac3a4bde33 100644 --- a/stdlib/public/Darwin/Foundation/URL.swift +++ b/stdlib/public/Darwin/Foundation/URL.swift @@ -631,11 +631,11 @@ public struct URL : ReferenceConvertible, Equatable { public init(fileURLWithFileSystemRepresentation path: UnsafePointer, isDirectory: Bool, relativeTo baseURL: __shared URL?) { _url = URL._converted(from: NSURL(fileURLWithFileSystemRepresentation: path, isDirectory: isDirectory, relativeTo: baseURL)) } - - public var hashValue: Int { - return _url.hash + + public func hash(into hasher: inout Hasher) { + hasher.combine(_url) } - + // MARK: - /// Returns the data representation of the URL's relativeString. diff --git a/stdlib/public/Darwin/Foundation/URLComponents.swift b/stdlib/public/Darwin/Foundation/URLComponents.swift index 2ba4914390e69..9c7c545f74124 100644 --- a/stdlib/public/Darwin/Foundation/URLComponents.swift +++ b/stdlib/public/Darwin/Foundation/URLComponents.swift @@ -292,8 +292,8 @@ public struct URLComponents : ReferenceConvertible, Hashable, Equatable, _Mutabl set { _applyMutation { $0.percentEncodedQueryItems = newValue } } } - public var hashValue: Int { - return _handle.map { $0.hash } + public func hash(into hasher: inout Hasher) { + hasher.combine(_handle._uncopiedReference()) } // MARK: - Bridging @@ -404,7 +404,9 @@ public struct URLQueryItem : ReferenceConvertible, Hashable, Equatable { set { _queryItem = NSURLQueryItem(name: name, value: newValue) } } - public var hashValue: Int { return _queryItem.hash } + public func hash(into hasher: inout Hasher) { + hasher.combine(_queryItem) + } @available(macOS 10.10, iOS 8.0, *) public static func ==(lhs: URLQueryItem, rhs: URLQueryItem) -> Bool { diff --git a/stdlib/public/Darwin/Foundation/URLRequest.swift b/stdlib/public/Darwin/Foundation/URLRequest.swift index b2695c7ad9cf5..66e32e850b146 100644 --- a/stdlib/public/Darwin/Foundation/URLRequest.swift +++ b/stdlib/public/Darwin/Foundation/URLRequest.swift @@ -229,10 +229,10 @@ public struct URLRequest : ReferenceConvertible, Equatable, Hashable { } } - public var hashValue: Int { - return _handle.map { $0.hashValue } + public func hash(into hasher: inout Hasher) { + hasher.combine(_handle._uncopiedReference()) } - + public static func ==(lhs: URLRequest, rhs: URLRequest) -> Bool { return lhs._handle._uncopiedReference().isEqual(rhs._handle._uncopiedReference()) } diff --git a/stdlib/public/Darwin/Foundation/UUID.swift b/stdlib/public/Darwin/Foundation/UUID.swift index aacaf654130bc..4f80cba7de429 100644 --- a/stdlib/public/Darwin/Foundation/UUID.swift +++ b/stdlib/public/Darwin/Foundation/UUID.swift @@ -74,14 +74,12 @@ public struct UUID : ReferenceConvertible, Hashable, Equatable, CustomStringConv } } - public var hashValue: Int { - return withUnsafePointer(to: uuid) { - $0.withMemoryRebound(to: UInt8.self, capacity: 16) { - return Int(bitPattern: CFHashBytes(UnsafeMutablePointer(mutating: $0), CFIndex(MemoryLayout.size))) - } + public func hash(into hasher: inout Hasher) { + withUnsafeBytes(of: uuid) { buffer in + hasher.combine(bytes: buffer) } } - + public var description: String { return uuidString } diff --git a/test/stdlib/NSStringAPI.swift b/test/stdlib/NSStringAPI.swift index a335a0b6aeab9..16ac3090792fb 100644 --- a/test/stdlib/NSStringAPI.swift +++ b/test/stdlib/NSStringAPI.swift @@ -91,6 +91,17 @@ NSStringAPIs.test("NSStringEncoding") { expectEqual(.utf8, enc) } +NSStringAPIs.test("NSStringEncoding.Hashable") { + let instances: [String.Encoding] = [ + .windowsCP1250, + .utf32LittleEndian, + .utf32BigEndian, + .ascii, + .utf8, + ] + checkHashable(instances, equalityOracle: { $0 == $1 }) +} + NSStringAPIs.test("localizedStringWithFormat(_:...)") { let world: NSString = "world" expectEqual("Hello, world!%42", String.localizedStringWithFormat( diff --git a/test/stdlib/TestAffineTransform.swift b/test/stdlib/TestAffineTransform.swift index a0b384a33ff80..defd61f20f989 100644 --- a/test/stdlib/TestAffineTransform.swift +++ b/test/stdlib/TestAffineTransform.swift @@ -315,58 +315,77 @@ class TestAffineTransform : TestAffineTransformSuper { checkPointTransformation(rotateAboutCenter, point: center, expectedPoint: center) } - func test_hashing_identity() { - let ref = NSAffineTransform() - let val = AffineTransform.identity - expectEqual(ref.hashValue, val.hashValue) - } - - func test_hashing_values() { + func test_hashing() { // the transforms are made up and the values don't matter - let values = [ - AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7), - AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33), - AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2), - AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99), - AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44) - ] - for val in values { - let ref = val as NSAffineTransform - expectEqual(ref.hashValue, val.hashValue) + let a = AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7) + let b = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33) + let c = AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2) + let d = AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99) + let e = AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44) + + // Samples testing that every component is properly hashed + let x1 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0) + let x2 = AffineTransform(m11: 1.5, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0) + let x3 = AffineTransform(m11: 1.0, m12: 2.5, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0) + let x4 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.5, m22: 4.0, tX: 5.0, tY: 6.0) + let x5 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.5, tX: 5.0, tY: 6.0) + let x6 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.5, tY: 6.0) + let x7 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.5) + + @inline(never) + func bridged(_ t: AffineTransform) -> NSAffineTransform { + return t as NSAffineTransform } - } - func test_AnyHashableContainingAffineTransform() { - let values: [AffineTransform] = [ - AffineTransform.identity, - AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33), - AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33) + let values: [[AffineTransform]] = [ + [AffineTransform.identity, NSAffineTransform() as AffineTransform], + [a, bridged(a) as AffineTransform], + [b, bridged(b) as AffineTransform], + [c, bridged(c) as AffineTransform], + [d, bridged(d) as AffineTransform], + [e, bridged(e) as AffineTransform], + [x1], [x2], [x3], [x4], [x5], [x6], [x7] ] - let anyHashables = values.map(AnyHashable.init) - expectEqual(AffineTransform.self, type(of: anyHashables[0].base)) - expectEqual(AffineTransform.self, type(of: anyHashables[1].base)) - expectEqual(AffineTransform.self, type(of: anyHashables[2].base)) - expectNotEqual(anyHashables[0], anyHashables[1]) - expectEqual(anyHashables[1], anyHashables[2]) + checkHashableGroups(values) } - func test_AnyHashableCreatedFromNSAffineTransform() { + func test_AnyHashable() { func makeNSAffineTransform(rotatedByDegrees angle: CGFloat) -> NSAffineTransform { let result = NSAffineTransform() result.rotate(byDegrees: angle) return result } - let values: [NSAffineTransform] = [ - makeNSAffineTransform(rotatedByDegrees: 0), - makeNSAffineTransform(rotatedByDegrees: 10), - makeNSAffineTransform(rotatedByDegrees: 10), + + let s1 = AffineTransform.identity + let s2 = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33) + let s3 = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33) + let s4 = makeNSAffineTransform(rotatedByDegrees: 10) as AffineTransform + let s5 = makeNSAffineTransform(rotatedByDegrees: 10) as AffineTransform + + let c1 = NSAffineTransform(transform: s1) + let c2 = NSAffineTransform(transform: s2) + let c3 = NSAffineTransform(transform: s3) + let c4 = makeNSAffineTransform(rotatedByDegrees: 10) + let c5 = makeNSAffineTransform(rotatedByDegrees: 10) + + let groups: [[AnyHashable]] = [ + [s1, c1], + [s2, c2, s3, c3], + [s4, c4, s5, c5] ] - let anyHashables = values.map(AnyHashable.init) - expectEqual(AffineTransform.self, type(of: anyHashables[0].base)) - expectEqual(AffineTransform.self, type(of: anyHashables[1].base)) - expectEqual(AffineTransform.self, type(of: anyHashables[2].base)) - expectNotEqual(anyHashables[0], anyHashables[1]) - expectEqual(anyHashables[1], anyHashables[2]) + checkHashableGroups(groups) + + expectEqual(AffineTransform.self, type(of: (s1 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (s2 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (s3 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (s4 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (s5 as AnyHashable).base)) + + expectEqual(AffineTransform.self, type(of: (c1 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (c2 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (c3 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (c4 as AnyHashable).base)) + expectEqual(AffineTransform.self, type(of: (c5 as AnyHashable).base)) } func test_unconditionallyBridgeFromObjectiveC() { @@ -400,10 +419,8 @@ AffineTransformTests.test("test_ScalingTranslation") { TestAffineTransform().tes AffineTransformTests.test("test_AppendTransform") { TestAffineTransform().test_AppendTransform() } AffineTransformTests.test("test_PrependTransform") { TestAffineTransform().test_PrependTransform() } AffineTransformTests.test("test_TransformComposition") { TestAffineTransform().test_TransformComposition() } -AffineTransformTests.test("test_hashing_identity") { TestAffineTransform().test_hashing_identity() } -AffineTransformTests.test("test_hashing_values") { TestAffineTransform().test_hashing_values() } -AffineTransformTests.test("test_AnyHashableContainingAffineTransform") { TestAffineTransform().test_AnyHashableContainingAffineTransform() } -AffineTransformTests.test("test_AnyHashableCreatedFromNSAffineTransform") { TestAffineTransform().test_AnyHashableCreatedFromNSAffineTransform() } +AffineTransformTests.test("test_hashing") { TestAffineTransform().test_hashing() } +AffineTransformTests.test("test_AnyHashable") { TestAffineTransform().test_AnyHashable() } AffineTransformTests.test("test_unconditionallyBridgeFromObjectiveC") { TestAffineTransform().test_unconditionallyBridgeFromObjectiveC() } AffineTransformTests.test("test_rotation_compose") { TestAffineTransform().test_rotation_compose() } runAllTests() diff --git a/test/stdlib/TestCalendar.swift b/test/stdlib/TestCalendar.swift index 31b332522c171..df339391578f5 100644 --- a/test/stdlib/TestCalendar.swift +++ b/test/stdlib/TestCalendar.swift @@ -109,8 +109,27 @@ class TestCalendar : TestCalendarSuper { current2.locale = Locale(identifier: "MyMadeUpLocale") expectNotEqual(current, current2) + } + + func test_hash() { + let calendars: [Calendar] = [ + Calendar.autoupdatingCurrent, + Calendar(identifier: .buddhist), + Calendar(identifier: .gregorian), + Calendar(identifier: .islamic), + Calendar(identifier: .iso8601), + ] + checkHashable(calendars, equalityOracle: { $0 == $1 }) + + // autoupdating calendar isn't equal to the current, even though it's + // likely to be the same. + let calendars2: [Calendar] = [ + Calendar.autoupdatingCurrent, + Calendar.current, + ] + checkHashable(calendars2, equalityOracle: { $0 == $1 }) } - + func test_properties() { // Mainly we want to just make sure these go through to the NSCalendar implementation at this point. if #available(iOS 8.0, OSX 10.7, *) { @@ -299,6 +318,7 @@ var CalendarTests = TestSuite("TestCalendar") CalendarTests.test("test_copyOnWrite") { TestCalendar().test_copyOnWrite() } CalendarTests.test("test_bridgingAutoupdating") { TestCalendar().test_bridgingAutoupdating() } CalendarTests.test("test_equality") { TestCalendar().test_equality() } +CalendarTests.test("test_hash") { TestCalendar().test_hash() } CalendarTests.test("test_properties") { TestCalendar().test_properties() } CalendarTests.test("test_AnyHashableContainingCalendar") { TestCalendar().test_AnyHashableContainingCalendar() } CalendarTests.test("test_AnyHashableCreatedFromNSCalendar") { TestCalendar().test_AnyHashableCreatedFromNSCalendar() } diff --git a/test/stdlib/TestCharacterSet.swift b/test/stdlib/TestCharacterSet.swift index edd3edeefaef8..293efa8b1cdc8 100644 --- a/test/stdlib/TestCharacterSet.swift +++ b/test/stdlib/TestCharacterSet.swift @@ -182,6 +182,21 @@ class TestCharacterSet : TestCharacterSetSuper { expectTrue(actualClassForCoder == expectedImmutable || actualClassForCoder == expectedMutable) } + func test_hashing() { + let a = CharacterSet(charactersIn: "ABC") + let b = CharacterSet(charactersIn: "CBA") + let c = CharacterSet(charactersIn: "bad") + let d = CharacterSet(charactersIn: "abd") + let e = CharacterSet.capitalizedLetters + let f = CharacterSet.lowercaseLetters + checkHashableGroups( + [[a, b], [c, d], [e], [f]], + // FIXME: CharacterSet delegates equality and hashing to + // CFCharacterSet, which uses unseeded hashing, so it's not + // complete. + allowIncompleteHashing: true) + } + func test_AnyHashableContainingCharacterSet() { let values: [CharacterSet] = [ CharacterSet(charactersIn: "ABC"), @@ -327,6 +342,7 @@ CharacterSetTests.test("testRanges") { TestCharacterSet().testRanges() } CharacterSetTests.test("testInsertAndRemove") { TestCharacterSet().testInsertAndRemove() } CharacterSetTests.test("testBasics") { TestCharacterSet().testBasics() } CharacterSetTests.test("test_classForCoder") { TestCharacterSet().test_classForCoder() } +CharacterSetTests.test("test_hashing") { TestCharacterSet().test_hashing() } CharacterSetTests.test("test_AnyHashableContainingCharacterSet") { TestCharacterSet().test_AnyHashableContainingCharacterSet() } CharacterSetTests.test("test_AnyHashableCreatedFromNSCharacterSet") { TestCharacterSet().test_AnyHashableCreatedFromNSCharacterSet() } CharacterSetTests.test("test_superSet") { TestCharacterSet().test_superSet() } diff --git a/test/stdlib/TestDate.swift b/test/stdlib/TestDate.swift index 6cc426cbc437c..e09c1de0010cb 100644 --- a/test/stdlib/TestDate.swift +++ b/test/stdlib/TestDate.swift @@ -130,6 +130,19 @@ class TestDate : TestDateSuper { expectEqual(1999, dc2.year) } + func test_DateHashing() { + let values: [Date] = [ + dateWithString("2010-05-17 14:49:47 -0700"), + dateWithString("2011-05-17 14:49:47 -0700"), + dateWithString("2010-06-17 14:49:47 -0700"), + dateWithString("2010-05-18 14:49:47 -0700"), + dateWithString("2010-05-17 15:49:47 -0700"), + dateWithString("2010-05-17 14:50:47 -0700"), + dateWithString("2010-05-17 14:49:48 -0700"), + ] + checkHashable(values, equalityOracle: { $0 == $1 }) + } + func test_AnyHashableContainingDate() { let values: [Date] = [ dateWithString("2016-05-17 14:49:47 -0700"), @@ -206,6 +219,7 @@ DateTests.test("testDistantFuture") { TestDate().testDistantFuture() } DateTests.test("testEquality") { TestDate().testEquality() } DateTests.test("testTimeIntervalSinceDate") { TestDate().testTimeIntervalSinceDate() } DateTests.test("testDateComponents") { TestDate().testDateComponents() } +DateTests.test("test_DateHashing") { TestDate().test_DateHashing() } DateTests.test("test_AnyHashableContainingDate") { TestDate().test_AnyHashableContainingDate() } DateTests.test("test_AnyHashableCreatedFromNSDate") { TestDate().test_AnyHashableCreatedFromNSDate() } DateTests.test("test_AnyHashableContainingDateComponents") { TestDate().test_AnyHashableContainingDateComponents() } diff --git a/test/stdlib/TestDateInterval.swift b/test/stdlib/TestDateInterval.swift index e942c4f7010aa..a5698150ba13b 100644 --- a/test/stdlib/TestDateInterval.swift +++ b/test/stdlib/TestDateInterval.swift @@ -65,6 +65,36 @@ class TestDateInterval : TestDateIntervalSuper { } } + func test_hashing() { + guard #available(iOS 10.10, OSX 10.12, tvOS 10.0, watchOS 3.0, *) else { return } + + let start1a = dateWithString("2019-04-04 17:09:23 -0700") + let start1b = dateWithString("2019-04-04 17:09:23 -0700") + let start2a = Date(timeIntervalSinceReferenceDate: start1a.timeIntervalSinceReferenceDate.nextUp) + let start2b = Date(timeIntervalSinceReferenceDate: start1a.timeIntervalSinceReferenceDate.nextUp) + let duration1 = 1800.0 + let duration2 = duration1.nextUp + let intervals: [[DateInterval]] = [ + [ + DateInterval(start: start1a, duration: duration1), + DateInterval(start: start1b, duration: duration1), + ], + [ + DateInterval(start: start1a, duration: duration2), + DateInterval(start: start1b, duration: duration2), + ], + [ + DateInterval(start: start2a, duration: duration1), + DateInterval(start: start2b, duration: duration1), + ], + [ + DateInterval(start: start2a, duration: duration2), + DateInterval(start: start2b, duration: duration2), + ], + ] + checkHashableGroups(intervals) + } + func test_checkIntersection() { if #available(iOS 10.10, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { let start1 = dateWithString("2010-05-17 14:49:47 -0700") @@ -171,6 +201,7 @@ class TestDateInterval : TestDateIntervalSuper { var DateIntervalTests = TestSuite("TestDateInterval") DateIntervalTests.test("test_compareDateIntervals") { TestDateInterval().test_compareDateIntervals() } DateIntervalTests.test("test_isEqualToDateInterval") { TestDateInterval().test_isEqualToDateInterval() } +DateIntervalTests.test("test_hashing") { TestDateInterval().test_hashing() } DateIntervalTests.test("test_checkIntersection") { TestDateInterval().test_checkIntersection() } DateIntervalTests.test("test_validIntersections") { TestDateInterval().test_validIntersections() } DateIntervalTests.test("test_AnyHashableContainingDateInterval") { TestDateInterval().test_AnyHashableContainingDateInterval() } diff --git a/test/stdlib/TestIndexPath.swift b/test/stdlib/TestIndexPath.swift index 79112b9ac9c8f..9043e7f450bff 100644 --- a/test/stdlib/TestIndexPath.swift +++ b/test/stdlib/TestIndexPath.swift @@ -222,12 +222,24 @@ class TestIndexPath: TestIndexPathSuper { } func testHashing() { - let ip1: IndexPath = [5, 1] - let ip2: IndexPath = [1, 1, 1] - - expectNotEqual(ip1.hashValue, ip2.hashValue) + let samples: [IndexPath] = [ + [], + [1], + [2], + [Int.max], + [1, 1], + [2, 1], + [1, 2], + [1, 1, 1], + [2, 1, 1], + [1, 2, 1], + [1, 1, 2], + [Int.max, Int.max, Int.max], + ] + checkHashable(samples, equalityOracle: { $0 == $1 }) - IndexPath(indexes: [Int.max >> 8, 2, Int.max >> 36]).hashValue // this should not cause an overflow crash + // this should not cause an overflow crash + _ = IndexPath(indexes: [Int.max >> 8, 2, Int.max >> 36]).hashValue } func testEquality() { diff --git a/test/stdlib/TestMeasurement.swift b/test/stdlib/TestMeasurement.swift index 295dfcaf2c221..d3ec9dbf70821 100644 --- a/test/stdlib/TestMeasurement.swift +++ b/test/stdlib/TestMeasurement.swift @@ -41,9 +41,8 @@ class MyDimensionalUnit : Dimension { } @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) -class BugUnit : Unit { +class CustomUnit : Unit { override init(symbol: String) { - precondition(symbol == "bug") super.init(symbol: symbol) } @@ -51,7 +50,8 @@ class BugUnit : Unit { super.init(coder: aDecoder) } - public static let bugs = BugUnit(symbol: "bug") + public static let bugs = CustomUnit(symbol: "bug") + public static let features = CustomUnit(symbol: "feature") } @available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) @@ -66,9 +66,9 @@ class TestMeasurement : TestMeasurementSuper { expectEqual(6, m3.value) expectEqual(m1, m2) - let m10 = Measurement(value: 2, unit: BugUnit.bugs) - let m11 = Measurement(value: 2, unit: BugUnit.bugs) - let m12 = Measurement(value: 3, unit: BugUnit.bugs) + let m10 = Measurement(value: 2, unit: CustomUnit.bugs) + let m11 = Measurement(value: 2, unit: CustomUnit.bugs) + let m12 = Measurement(value: 3, unit: CustomUnit.bugs) expectEqual(m10, m11) expectNotEqual(m10, m12) @@ -89,7 +89,7 @@ class TestMeasurement : TestMeasurementSuper { // This correctly fails to build - // let m2 = Measurement(value: 1, unit: BugUnit.bugs) + // let m2 = Measurement(value: 1, unit: CustomUnit.bugs) // m2.converted(to: MyDimensionalUnit.unitKiloA) } @@ -113,9 +113,9 @@ class TestMeasurement : TestMeasurementSuper { // Dynamically different dimensions expectEqual(Measurement(value: 1_001_000, unit: MyDimensionalUnit.unitA), oneMegaA + oneKiloA) - var bugCount = Measurement(value: 1, unit: BugUnit.bugs) + var bugCount = Measurement(value: 1, unit: CustomUnit.bugs) expectEqual(bugCount.value, 1) - bugCount = bugCount + Measurement(value: 4, unit: BugUnit.bugs) + bugCount = bugCount + Measurement(value: 4, unit: CustomUnit.bugs) expectEqual(bugCount.value, 5) } @@ -154,6 +154,51 @@ class TestMeasurement : TestMeasurementSuper { expectTrue(fiveKM <= fiveThousandM) } + func testHashing() { + let lengths: [[Measurement]] = [ + [ + Measurement(value: 5, unit: UnitLength.kilometers), + Measurement(value: 5000, unit: UnitLength.meters), + Measurement(value: 5000, unit: UnitLength.meters), + ], + [ + Measurement(value: 1, unit: UnitLength.kilometers), + Measurement(value: 1000, unit: UnitLength.meters), + ], + [ + Measurement(value: 1, unit: UnitLength.meters), + Measurement(value: 1000, unit: UnitLength.millimeters), + ], + ] + checkHashableGroups(lengths) + + let durations: [[Measurement]] = [ + [ + Measurement(value: 3600, unit: UnitDuration.seconds), + Measurement(value: 60, unit: UnitDuration.minutes), + Measurement(value: 1, unit: UnitDuration.hours), + ], + [ + Measurement(value: 1800, unit: UnitDuration.seconds), + Measurement(value: 30, unit: UnitDuration.minutes), + Measurement(value: 0.5, unit: UnitDuration.hours), + ] + ] + checkHashableGroups(durations) + + let custom: [Measurement] = [ + Measurement(value: 1, unit: CustomUnit.bugs), + Measurement(value: 2, unit: CustomUnit.bugs), + Measurement(value: 3, unit: CustomUnit.bugs), + Measurement(value: 4, unit: CustomUnit.bugs), + Measurement(value: 1, unit: CustomUnit.features), + Measurement(value: 2, unit: CustomUnit.features), + Measurement(value: 3, unit: CustomUnit.features), + Measurement(value: 4, unit: CustomUnit.features), + ] + checkHashable(custom, equalityOracle: { $0 == $1 }) + } + func test_AnyHashableContainingMeasurement() { let values: [Measurement] = [ Measurement(value: 100, unit: UnitLength.meters), @@ -193,6 +238,7 @@ if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) { MeasurementTests.test("testMeasurementFormatter") { TestMeasurement().testMeasurementFormatter() } MeasurementTests.test("testEquality") { TestMeasurement().testEquality() } MeasurementTests.test("testComparison") { TestMeasurement().testComparison() } + MeasurementTests.test("testHashing") { TestMeasurement().testHashing() } MeasurementTests.test("test_AnyHashableContainingMeasurement") { TestMeasurement().test_AnyHashableContainingMeasurement() } MeasurementTests.test("test_AnyHashableCreatedFromNSMeasurement") { TestMeasurement().test_AnyHashableCreatedFromNSMeasurement() } runAllTests() diff --git a/test/stdlib/TestNSRange.swift b/test/stdlib/TestNSRange.swift index 0d49866ba9228..3196ef9b0b7ee 100644 --- a/test/stdlib/TestNSRange.swift +++ b/test/stdlib/TestNSRange.swift @@ -84,13 +84,17 @@ class TestNSRange : TestNSRangeSuper { } func testHashing() { - let r1 = NSRange(location: 10, length: 22) - let r2 = NSRange(location: 10, length: 22) - let r3 = NSRange(location: 1, length: 22) - expectEqual(r1.hashValue, r2.hashValue) - expectNotEqual(r1.hashValue, r3.hashValue) - let rangeSet: Set = [r1, r2, r3] - expectEqual(2, rangeSet.count) + let large = Int.max >> 2 + let samples: [NSRange] = [ + NSRange(location: 1, length: 1), + NSRange(location: 1, length: 2), + NSRange(location: 2, length: 1), + NSRange(location: 2, length: 2), + NSRange(location: large, length: large), + NSRange(location: 0, length: large), + NSRange(location: large, length: 0), + ] + checkHashable(samples, equalityOracle: { $0 == $1 }) } func testBounding() { diff --git a/test/stdlib/TestNotification.swift b/test/stdlib/TestNotification.swift index 837c192bf4824..f4191b23b3012 100644 --- a/test/stdlib/TestNotification.swift +++ b/test/stdlib/TestNotification.swift @@ -32,11 +32,74 @@ class TestNotification : TestNotificationSuper { func test_unconditionallyBridgeFromObjectiveC() { expectEqual(Notification(name: Notification.Name("")), Notification._unconditionallyBridgeFromObjectiveC(nil)) } + + func test_hashing() { + let o1 = NSObject() + let o2 = NSObject() + let values: [Notification] = [ + /* 0 */ Notification(name: .init("a"), object: o1, userInfo: nil), + /* 1 */ Notification(name: .init("a"), object: o2, userInfo: nil), + /* 2 */ Notification(name: .init("b"), object: o1, userInfo: nil), + /* 3 */ Notification(name: .init("b"), object: o2, userInfo: nil), + /* 4 */ Notification(name: .init("a"), object: o1, userInfo: ["Foo": 1]), + /* 5 */ Notification(name: .init("a"), object: o1, userInfo: ["Foo": 2]), + /* 6 */ Notification(name: .init("a"), object: o1, userInfo: ["Bar": 1]), + /* 7 */ Notification(name: .init("a"), object: o1, userInfo: ["Foo": 1, "Bar": 2]), + ] + + let hashGroups: [Int: Int] = [ + 0: 0, + 1: 0, + 2: 1, + 3: 1, + 4: 2, + 5: 2, + 6: 3, + 7: 4 + ] + + checkHashable( + values, + equalityOracle: { $0 == $1 }, + hashEqualityOracle: { + // FIXME: Unfortunately while we have 8 different notifications, + // three pairs of them have colliding hash encodings. + hashGroups[$0] == hashGroups[$1] + }) + } } #if !FOUNDATION_XCTEST var NotificationTests = TestSuite("TestNotification") NotificationTests.test("test_unconditionallyBridgeFromObjectiveC") { TestNotification().test_unconditionallyBridgeFromObjectiveC() } +NotificationTests.test("test_hashing") { TestNotification().test_hashing() } + +private struct NonHashableValueType: Equatable { + let value: Int + init(_ value: Int) { + self.value = value + } +} + +NotificationTests.test("test_reflexivity_violation") + .xfail( + .custom({ true }, + reason: " Foundation.Notification's equality relation isn't reflexive")) + .code { + let name = Notification.Name("name") + let a = NonHashableValueType(1) + let b = NonHashableValueType(2) + // Currently none of these values compare equal to themselves: + let values: [Notification] = [ + Notification(name: name, object: a, userInfo: nil), + Notification(name: name, object: b, userInfo: nil), + Notification(name: name, object: nil, userInfo: ["foo": a]), + Notification(name: name, object: nil, userInfo: ["foo": b]), + ] + checkHashable(values, equalityOracle: { $0 == $1 }) +} + + runAllTests() #endif diff --git a/test/stdlib/TestPersonNameComponents.swift b/test/stdlib/TestPersonNameComponents.swift index 6c7562456788e..824fc657cce7a 100644 --- a/test/stdlib/TestPersonNameComponents.swift +++ b/test/stdlib/TestPersonNameComponents.swift @@ -29,6 +29,38 @@ class TestPersonNameComponents : TestPersonNameComponentsSuper { result.familyName = familyName return result } + + func test_Hashing() { + guard #available(macOS 10.13, iOS 11.0, *) else { + // PersonNameComponents was available in earlier versions, but its + // hashing did not match its definition for equality. + return + } + + let values: [[PersonNameComponents]] = [ + [ + makePersonNameComponents(givenName: "Kevin", familyName: "Frank"), + makePersonNameComponents(givenName: "Kevin", familyName: "Frank"), + ], + [ + makePersonNameComponents(givenName: "John", familyName: "Frank"), + makePersonNameComponents(givenName: "John", familyName: "Frank"), + ], + [ + makePersonNameComponents(givenName: "Kevin", familyName: "Appleseed"), + makePersonNameComponents(givenName: "Kevin", familyName: "Appleseed"), + ], + [ + makePersonNameComponents(givenName: "John", familyName: "Appleseed"), + makePersonNameComponents(givenName: "John", familyName: "Appleseed"), + ] + ] + checkHashableGroups( + values, + // FIXME: PersonNameComponents hashes aren't seeded. + allowIncompleteHashing: true) + } + func test_AnyHashableContainingPersonNameComponents() { if #available(OSX 10.11, iOS 9.0, *) { let values: [PersonNameComponents] = [ @@ -72,6 +104,7 @@ class TestPersonNameComponents : TestPersonNameComponentsSuper { #if !FOUNDATION_XCTEST var PersonNameComponentsTests = TestSuite("TestPersonNameComponents") +PersonNameComponentsTests.test("test_Hashing") { TestPersonNameComponents().test_Hashing() } PersonNameComponentsTests.test("test_AnyHashableContainingPersonNameComponents") { TestPersonNameComponents().test_AnyHashableContainingPersonNameComponents() } PersonNameComponentsTests.test("test_AnyHashableCreatedFromNSPersonNameComponents") { TestPersonNameComponents().test_AnyHashableCreatedFromNSPersonNameComponents() } runAllTests() diff --git a/test/stdlib/TestUUID.swift b/test/stdlib/TestUUID.swift index 90161bcdcef50..8f5b0f4bb7e08 100644 --- a/test/stdlib/TestUUID.swift +++ b/test/stdlib/TestUUID.swift @@ -85,9 +85,28 @@ class TestUUID : TestUUIDSuper { } func test_hash() { - let ref = NSUUID() - let val = UUID(uuidString: ref.uuidString)! - expectEqual(ref.hashValue, val.hashValue, "Hashes of references and values should be identical") + let values: [UUID] = [ + // This list takes a UUID and tweaks every byte while + // leaving the version/variant intact. + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a63baa1c-b4f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53caa1c-b4f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53bab1c-b4f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1d-b4f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b5f5-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f6-48db-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-49db-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48dc-9467-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9567-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9468-9786b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9886b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9787b76b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b86b256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76c256c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b266c")!, + UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b256d")!, + ] + checkHashable(values, equalityOracle: { $0 == $1 }) } func test_AnyHashableContainingUUID() {