Skip to content

Commit 2e85997

Browse files
Merge pull request #24369 from lorentey/foundation-hashing-5.1-2019-04-24
[5.1 04-24-2019][Foundation] Modernize hashing in Foundation's Swift-only types
2 parents 6c3cac7 + 3355782 commit 2e85997

33 files changed

+518
-167
lines changed

stdlib/private/StdlibUnittest/StdlibUnittest.swift

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,17 +2346,22 @@ internal func _checkEquatableImpl<Instance : Equatable>(
23462346
let isEqualXY = x == y
23472347
expectEqual(
23482348
predictedXY, isEqualXY,
2349-
(predictedXY
2350-
? "expected equal, found not equal\n"
2351-
: "expected not equal, found equal\n") +
2352-
"lhs (at index \(i)): \(String(reflecting: x))\n" +
2353-
"rhs (at index \(j)): \(String(reflecting: y))",
2349+
"""
2350+
\((predictedXY
2351+
? "expected equal, found not equal"
2352+
: "expected not equal, found equal"))
2353+
lhs (at index \(i)): \(String(reflecting: x))
2354+
rhs (at index \(j)): \(String(reflecting: y))
2355+
""",
23542356
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
23552357

23562358
// Not-equal is an inverse of equal.
23572359
expectNotEqual(
23582360
isEqualXY, x != y,
2359-
"lhs (at index \(i)): \(String(reflecting: x))\nrhs (at index \(j)): \(String(reflecting: y))",
2361+
"""
2362+
lhs (at index \(i)): \(String(reflecting: x))
2363+
rhs (at index \(j)): \(String(reflecting: y))
2364+
""",
23602365
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
23612366

23622367
if !allowBrokenTransitivity {
@@ -2398,6 +2403,10 @@ public func checkEquatable<T : Equatable>(
23982403
showFrame: false)
23992404
}
24002405

2406+
/// Produce an integer hash value for `value` by feeding it to a dedicated
2407+
/// `Hasher`. This is always done by calling the `hash(into:)` method.
2408+
/// If a non-nil `seed` is given, it is used to perturb the hasher state;
2409+
/// this is useful for resolving accidental hash collisions.
24012410
internal func hash<H: Hashable>(_ value: H, seed: Int? = nil) -> Int {
24022411
var hasher = Hasher()
24032412
if let seed = seed {
@@ -2413,6 +2422,7 @@ internal func hash<H: Hashable>(_ value: H, seed: Int? = nil) -> Int {
24132422
public func checkHashableGroups<Groups: Collection>(
24142423
_ groups: Groups,
24152424
_ message: @autoclosure () -> String = "",
2425+
allowIncompleteHashing: Bool = false,
24162426
stackTrace: SourceLocStack = SourceLocStack(),
24172427
showFrame: Bool = true,
24182428
file: String = #file, line: UInt = #line
@@ -2430,6 +2440,7 @@ public func checkHashableGroups<Groups: Collection>(
24302440
equalityOracle: equalityOracle,
24312441
hashEqualityOracle: equalityOracle,
24322442
allowBrokenTransitivity: false,
2443+
allowIncompleteHashing: allowIncompleteHashing,
24332444
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line),
24342445
showFrame: false)
24352446
}
@@ -2441,6 +2452,7 @@ public func checkHashable<Instances: Collection>(
24412452
_ instances: Instances,
24422453
equalityOracle: (Instances.Index, Instances.Index) -> Bool,
24432454
allowBrokenTransitivity: Bool = false,
2455+
allowIncompleteHashing: Bool = false,
24442456
_ message: @autoclosure () -> String = "",
24452457
stackTrace: SourceLocStack = SourceLocStack(),
24462458
showFrame: Bool = true,
@@ -2451,6 +2463,7 @@ public func checkHashable<Instances: Collection>(
24512463
equalityOracle: equalityOracle,
24522464
hashEqualityOracle: equalityOracle,
24532465
allowBrokenTransitivity: allowBrokenTransitivity,
2466+
allowIncompleteHashing: allowIncompleteHashing,
24542467
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line),
24552468
showFrame: false)
24562469
}
@@ -2464,6 +2477,7 @@ public func checkHashable<Instances: Collection>(
24642477
equalityOracle: (Instances.Index, Instances.Index) -> Bool,
24652478
hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool,
24662479
allowBrokenTransitivity: Bool = false,
2480+
allowIncompleteHashing: Bool = false,
24672481
_ message: @autoclosure () -> String = "",
24682482
stackTrace: SourceLocStack = SourceLocStack(),
24692483
showFrame: Bool = true,
@@ -2516,12 +2530,12 @@ public func checkHashable<Instances: Collection>(
25162530
expectEqual(
25172531
x._rawHashValue(seed: 0), y._rawHashValue(seed: 0),
25182532
"""
2519-
_rawHashValue expected to match, found to differ
2533+
_rawHashValue(seed:) expected to match, found to differ
25202534
lhs (at index \(i)): \(x)
25212535
rhs (at index \(j)): \(y)
25222536
""",
25232537
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line))
2524-
} else {
2538+
} else if !allowIncompleteHashing {
25252539
// Try a few different seeds; at least one of them should discriminate
25262540
// between the hashes. It is extremely unlikely this check will fail
25272541
// all ten attempts, unless the type's hash encoding is not unique,

stdlib/public/Darwin/Foundation/AffineTransform.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,13 @@ public struct AffineTransform : ReferenceConvertible, Hashable, CustomStringConv
276276
return newSize
277277
}
278278

279-
public var hashValue : Int {
280-
return Int(m11 + m12 + m21 + m22 + tX + tY)
279+
public func hash(into hasher: inout Hasher) {
280+
hasher.combine(m11)
281+
hasher.combine(m12)
282+
hasher.combine(m21)
283+
hasher.combine(m22)
284+
hasher.combine(tX)
285+
hasher.combine(tY)
281286
}
282287

283288
public var description: String {

stdlib/public/Darwin/Foundation/Calendar.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -899,15 +899,16 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi
899899

900900
// MARK: -
901901

902-
public var hashValue : Int {
903-
// We implement hash ourselves, because we need to make sure autoupdating calendars have the same hash
902+
public func hash(into hasher: inout Hasher) {
903+
// We need to make sure autoupdating calendars have the same hash
904904
if _autoupdating {
905-
return 1
905+
hasher.combine(false)
906906
} else {
907-
return _handle.map { $0.hash }
907+
hasher.combine(true)
908+
hasher.combine(_handle.map { $0 })
908909
}
909910
}
910-
911+
911912
// MARK: -
912913
// MARK: Conversion Functions
913914

stdlib/public/Darwin/Foundation/CharacterSet.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ fileprivate final class __CharacterSetStorage : Hashable {
5252

5353
// MARK: -
5454

55-
fileprivate var hashValue : Int {
55+
fileprivate func hash(into hasher: inout Hasher) {
5656
switch _backing {
5757
case .immutable(let cs):
58-
return Int(CFHash(cs))
58+
hasher.combine(CFHash(cs))
5959
case .mutable(let cs):
60-
return Int(CFHash(cs))
60+
hasher.combine(CFHash(cs))
6161
}
6262
}
63-
63+
6464
fileprivate static func ==(lhs : __CharacterSetStorage, rhs : __CharacterSetStorage) -> Bool {
6565
switch (lhs._backing, rhs._backing) {
6666
case (.immutable(let cs1), .immutable(let cs2)):
@@ -754,8 +754,8 @@ public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgeb
754754

755755
// MARK: -
756756

757-
public var hashValue: Int {
758-
return _storage.hashValue
757+
public func hash(into hasher: inout Hasher) {
758+
hasher.combine(_storage)
759759
}
760760

761761
/// Returns true if the two `CharacterSet`s are equal.

stdlib/public/Darwin/Foundation/Date.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,10 @@ public struct Date : ReferenceConvertible, Comparable, Equatable {
142142
*/
143143
public static let distantPast = Date(timeIntervalSinceReferenceDate: -63114076800.0)
144144

145-
public var hashValue: Int {
146-
if #available(macOS 10.12, iOS 10.0, *) {
147-
return Int(bitPattern: __CFHashDouble(_time))
148-
} else { // 10.11 and previous behavior fallback; this must allocate a date to reference the hash value and then throw away the reference
149-
return NSDate(timeIntervalSinceReferenceDate: _time).hash
150-
}
145+
public func hash(into hasher: inout Hasher) {
146+
hasher.combine(_time)
151147
}
152-
148+
153149
/// Compare two `Date` values.
154150
public func compare(_ other: Date) -> ComparisonResult {
155151
if _time < other.timeIntervalSinceReferenceDate {

stdlib/public/Darwin/Foundation/DateComponents.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,10 @@ public struct DateComponents : ReferenceConvertible, Hashable, Equatable, _Mutab
263263

264264
// MARK: -
265265

266-
public var hashValue : Int {
267-
return _handle.map { $0.hash }
266+
public func hash(into hasher: inout Hasher) {
267+
hasher.combine(_handle._uncopiedReference())
268268
}
269-
269+
270270
// MARK: - Bridging Helpers
271271

272272
fileprivate init(reference: __shared NSDateComponents) {

stdlib/public/Darwin/Foundation/DateInterval.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,11 @@ public struct DateInterval : ReferenceConvertible, Comparable, Hashable, Codable
155155
return false
156156
}
157157

158-
public var hashValue: Int {
159-
var buf: (UInt, UInt) = (UInt(start.timeIntervalSinceReferenceDate), UInt(end.timeIntervalSinceReferenceDate))
160-
return withUnsafeMutablePointer(to: &buf) {
161-
$0.withMemoryRebound(to: UInt8.self, capacity: 2 * MemoryLayout<UInt>.size / MemoryLayout<UInt8>.size) {
162-
return Int(bitPattern: CFHashBytes($0, CFIndex(MemoryLayout<UInt>.size * 2)))
163-
}
164-
}
158+
public func hash(into hasher: inout Hasher) {
159+
hasher.combine(start)
160+
hasher.combine(duration)
165161
}
166-
162+
167163
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
168164
public static func ==(lhs: DateInterval, rhs: DateInterval) -> Bool {
169165
return lhs.start == rhs.start && lhs.duration == rhs.duration

stdlib/public/Darwin/Foundation/Decimal.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,12 @@ extension Decimal : Hashable, Comparable {
177177
return _isNegative != 0 ? -d : d
178178
}
179179

180-
public var hashValue: Int {
181-
return Int(bitPattern: __CFHashDouble(doubleValue))
180+
public func hash(into hasher: inout Hasher) {
181+
// FIXME: This is a weak hash. We should rather normalize self to a
182+
// canonical member of the exact same equivalence relation that
183+
// NSDecimalCompare implements, then simply feed all components to the
184+
// hasher.
185+
hasher.combine(doubleValue)
182186
}
183187

184188
public static func ==(lhs: Decimal, rhs: Decimal) -> Bool {

stdlib/public/Darwin/Foundation/IndexPath.swift

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -662,25 +662,31 @@ public struct IndexPath : ReferenceConvertible, Equatable, Hashable, MutableColl
662662
return .orderedSame
663663
}
664664

665-
public var hashValue: Int {
666-
func hashIndexes(first: Int, last: Int, count: Int) -> Int {
667-
let totalBits = MemoryLayout<Int>.size * 8
668-
let lengthBits = 8
669-
let firstIndexBits = (totalBits - lengthBits) / 2
670-
return count &+ (first << lengthBits) &+ (last << (lengthBits + firstIndexBits))
671-
}
672-
665+
public func hash(into hasher: inout Hasher) {
666+
// Note: We compare all indices in ==, so for proper hashing, we must
667+
// also feed them all to the hasher.
668+
//
669+
// To ensure we have unique hash encodings in nested hashing contexts,
670+
// we combine the count of indices as well as the indices themselves.
671+
// (This matches what Array does.)
673672
switch _indexes {
674-
case .empty: return 0
675-
case .single(let index): return index.hashValue
676-
case .pair(let first, let second):
677-
return hashIndexes(first: first, last: second, count: 2)
678-
default:
679-
let cnt = _indexes.count
680-
return hashIndexes(first: _indexes[0], last: _indexes[cnt - 1], count: cnt)
673+
case .empty:
674+
hasher.combine(0)
675+
case let .single(index):
676+
hasher.combine(1)
677+
hasher.combine(index)
678+
case let .pair(first, second):
679+
hasher.combine(2)
680+
hasher.combine(first)
681+
hasher.combine(second)
682+
case let .array(indexes):
683+
hasher.combine(indexes.count)
684+
for index in indexes {
685+
hasher.combine(index)
686+
}
681687
}
682688
}
683-
689+
684690
// MARK: - Bridging Helpers
685691

686692
fileprivate init(nsIndexPath: __shared ReferenceType) {

stdlib/public/Darwin/Foundation/IndexSet.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ public struct IndexSet : ReferenceConvertible, Equatable, BidirectionalCollectio
137137
_handle = _MutablePairHandle(NSIndexSet(), copying: false)
138138
}
139139

140-
public var hashValue: Int {
141-
return _handle.map { $0.hash }
140+
public func hash(into hasher: inout Hasher) {
141+
_handle.map { hasher.combine($0) }
142142
}
143143

144144
/// Returns the number of integers in `self`.

stdlib/public/Darwin/Foundation/Locale.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,12 @@ public struct Locale : Hashable, Equatable, ReferenceConvertible {
405405
// MARK: -
406406
//
407407

408-
public var hashValue : Int {
408+
public func hash(into hasher: inout Hasher) {
409409
if _autoupdating {
410-
return 1
410+
hasher.combine(false)
411411
} else {
412-
return _wrapped.hash
412+
hasher.combine(true)
413+
hasher.combine(_wrapped)
413414
}
414415
}
415416

stdlib/public/Darwin/Foundation/Measurement.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,19 @@ public struct Measurement<UnitType : Unit> : ReferenceConvertible, Comparable, E
3636
self.unit = unit
3737
}
3838

39-
public var hashValue: Int {
40-
return Int(bitPattern: __CFHashDouble(value))
39+
public func hash(into hasher: inout Hasher) {
40+
// Warning: The canonicalization performed here needs to be kept in
41+
// perfect sync with the definition of == below. The floating point
42+
// values that are compared there must match exactly with the values fed
43+
// to the hasher here, or hashing would break.
44+
if let dimension = unit as? Dimension {
45+
// We don't need to feed the base unit to the hasher here; all
46+
// dimensional measurements of the same type share the same unit.
47+
hasher.combine(dimension.converter.baseUnitValue(fromValue: value))
48+
} else {
49+
hasher.combine(unit)
50+
hasher.combine(value)
51+
}
4152
}
4253
}
4354

@@ -170,6 +181,10 @@ extension Measurement {
170181
/// 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.
171182
/// - returns: `true` if the measurements are equal.
172183
public static func ==<LeftHandSideType, RightHandSideType>(lhs: Measurement<LeftHandSideType>, rhs: Measurement<RightHandSideType>) -> Bool {
184+
// Warning: This defines an equivalence relation that needs to be kept
185+
// in perfect sync with the hash(into:) definition above. The floating
186+
// point values that are fed to the hasher there must match exactly with
187+
// the values compared here, or hashing would break.
173188
if lhs.unit == rhs.unit {
174189
return lhs.value == rhs.value
175190
} else {

stdlib/public/Darwin/Foundation/NSRange.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@
1313
@_exported import Foundation // Clang module
1414

1515
extension NSRange : Hashable {
16-
public var hashValue: Int {
17-
#if arch(i386) || arch(arm)
18-
return Int(bitPattern: (UInt(bitPattern: location) | (UInt(bitPattern: length) << 16)))
19-
#elseif arch(x86_64) || arch(arm64)
20-
return Int(bitPattern: (UInt(bitPattern: location) | (UInt(bitPattern: length) << 32)))
21-
#endif
16+
public func hash(into hasher: inout Hasher) {
17+
hasher.combine(location)
18+
hasher.combine(length)
2219
}
2320

2421
public static func==(lhs: NSRange, rhs: NSRange) -> Bool {
@@ -230,4 +227,4 @@ extension NSRange : Codable {
230227
try container.encode(self.location)
231228
try container.encode(self.length)
232229
}
233-
}
230+
}

stdlib/public/Darwin/Foundation/NSStringEncodings.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ extension String {
5151
}
5252

5353
extension String.Encoding : Hashable {
54-
public var hashValue : Int {
55-
return rawValue.hashValue
54+
public func hash(into hasher: inout Hasher) {
55+
hasher.combine(rawValue)
5656
}
5757

5858
public static func ==(lhs: String.Encoding, rhs: String.Encoding) -> Bool {

0 commit comments

Comments
 (0)