Skip to content

Commit a66fe70

Browse files
authored
Merge pull request #2118 from lorentey/foundation-hashing
Modernize hashing in Foundation's Swift-only types
2 parents 87f86c6 + d69debd commit a66fe70

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+736
-160
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ if(ENABLE_TESTING)
390390
TestFoundation/TestCodable.swift
391391
TestFoundation/TestDateComponents.swift
392392
TestFoundation/TestDateFormatter.swift
393+
TestFoundation/TestDateInterval.swift
393394
TestFoundation/TestDateIntervalFormatter.swift
394395
TestFoundation/TestDate.swift
395396
TestFoundation/TestDecimal.swift
@@ -407,6 +408,7 @@ if(ENABLE_TESTING)
407408
TestFoundation/TestJSONSerialization.swift
408409
TestFoundation/TestLengthFormatter.swift
409410
TestFoundation/TestMassFormatter.swift
411+
TestFoundation/TestMeasurement.swift
410412
TestFoundation/TestNotificationCenter.swift
411413
TestFoundation/TestNotificationQueue.swift
412414
TestFoundation/TestNotification.swift

Foundation.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@
354354
7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */; };
355355
7D0DE86E211883F500540061 /* TestDateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0DE86C211883F500540061 /* TestDateComponents.swift */; };
356356
7D0DE86F211883F500540061 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0DE86D211883F500540061 /* Utilities.swift */; };
357+
7D8BD739225ED1480057CF37 /* TestMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D8BD738225ED1480057CF37 /* TestMeasurement.swift */; };
358+
7D8BD737225EADB80057CF37 /* TestDateInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D8BD736225EADB80057CF37 /* TestDateInterval.swift */; };
357359
90E645DF1E4C89A400D0D47C /* TestNSCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E645DE1E4C89A400D0D47C /* TestNSCache.swift */; };
358360
91B668A32252B3C5001487A1 /* FileManager+POSIX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B668A22252B3C5001487A1 /* FileManager+POSIX.swift */; };
359361
91B668A52252B3E7001487A1 /* FileManager+Win32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B668A42252B3E7001487A1 /* FileManager+Win32.swift */; };
@@ -881,6 +883,8 @@
881883
7A7D6FBA1C16439400957E2E /* TestURLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestURLResponse.swift; sourceTree = "<group>"; };
882884
7D0DE86C211883F500540061 /* TestDateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDateComponents.swift; sourceTree = "<group>"; };
883885
7D0DE86D211883F500540061 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
886+
7D8BD738225ED1480057CF37 /* TestMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMeasurement.swift; sourceTree = "<group>"; };
887+
7D8BD736225EADB80057CF37 /* TestDateInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDateInterval.swift; sourceTree = "<group>"; };
884888
83712C8D1C1684900049AD49 /* TestNSURLRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLRequest.swift; sourceTree = "<group>"; };
885889
844DC3321C17584F005611F9 /* TestScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestScanner.swift; sourceTree = "<group>"; };
886890
848A30571C137B3500C83206 /* TestHTTPCookie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestHTTPCookie.swift; path = TestFoundation/TestHTTPCookie.swift; sourceTree = SOURCE_ROOT; };
@@ -1586,6 +1590,7 @@
15861590
7D0DE86D211883F500540061 /* Utilities.swift */,
15871591
155D3BBB22401D1100B0D38E /* FixtureValues.swift */,
15881592
7D0DE86C211883F500540061 /* TestDateComponents.swift */,
1593+
7D8BD736225EADB80057CF37 /* TestDateInterval.swift */,
15891594
6E203B8C1C1303BB003B2576 /* TestBundle.swift */,
15901595
A5A34B551C18C85D00FD972B /* TestByteCountFormatter.swift */,
15911596
2EBE67A31C77BF05006583D5 /* TestDateFormatter.swift */,
@@ -1668,6 +1673,7 @@
16681673
5B6F17961C48631C00935030 /* TestUtils.swift */,
16691674
03B6F5831F15F339004F25AF /* TestURLProtocol.swift */,
16701675
3E55A2321F52463B00082000 /* TestUnit.swift */,
1676+
7D8BD738225ED1480057CF37 /* TestMeasurement.swift */,
16711677
);
16721678
name = Tests;
16731679
sourceTree = "<group>";
@@ -2661,6 +2667,7 @@
26612667
2EBE67A51C77BF0E006583D5 /* TestDateFormatter.swift in Sources */,
26622668
5B13B3291C582D4C00651CE2 /* TestCalendar.swift in Sources */,
26632669
158BCCAA2220A12600750239 /* TestDateIntervalFormatter.swift in Sources */,
2670+
7D8BD737225EADB80057CF37 /* TestDateInterval.swift in Sources */,
26642671
5B13B34F1C582D4C00651CE2 /* TestXMLParser.swift in Sources */,
26652672
BF85E9D81FBDCC2000A79793 /* TestHost.swift in Sources */,
26662673
D5C40F331CDA1D460005690C /* TestOperationQueue.swift in Sources */,
@@ -2678,6 +2685,7 @@
26782685
5B13B3511C582D4C00651CE2 /* TestByteCountFormatter.swift in Sources */,
26792686
BDFDF0A71DFF5B3E00C04CC5 /* TestPersonNameComponents.swift in Sources */,
26802687
5B13B3501C582D4C00651CE2 /* TestUtils.swift in Sources */,
2688+
7D8BD739225ED1480057CF37 /* TestMeasurement.swift in Sources */,
26812689
CD1C7F7D1E303B47008E331C /* TestNSError.swift in Sources */,
26822690
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */,
26832691
5B13B3431C582D4C00651CE2 /* TestScanner.swift in Sources */,

Foundation/AffineTransform.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,13 @@ public struct AffineTransform : ReferenceConvertible, Hashable, CustomStringConv
253253
return newSize
254254
}
255255

256-
/// The computed hash value for the transform.
257-
public var hashValue : Int {
258-
return Int((m11 + m12 + m21 + m22 + tX + tY).native)
256+
public func hash(into hasher: inout Hasher) {
257+
hasher.combine(m11)
258+
hasher.combine(m12)
259+
hasher.combine(m21)
260+
hasher.combine(m22)
261+
hasher.combine(tX)
262+
hasher.combine(tY)
259263
}
260264

261265
/// A textual description of the transform.

Foundation/Boxing.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ internal protocol _SwiftNativeFoundationType : class {
7575

7676
func mutableCopy(with zone : NSZone) -> Any
7777

78+
func hash(into hasher: inout Hasher)
7879
var hashValue: Int { get }
80+
7981
var description: String { get }
8082
var debugDescription: String { get }
8183

@@ -115,6 +117,10 @@ extension _SwiftNativeFoundationType {
115117
return _mapUnmanaged { ($0 as NSObject).mutableCopy() }
116118
}
117119

120+
func hash(into hasher: inout Hasher) {
121+
_mapUnmanaged { hasher.combine($0) }
122+
}
123+
118124
var hashValue: Int {
119125
return _mapUnmanaged { return $0.hashValue }
120126
}

Foundation/CGFloat.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,16 @@ extension CGFloat : Hashable {
477477
public var hashValue: Int {
478478
return native.hashValue
479479
}
480+
481+
@_transparent
482+
public func hash(into hasher: inout Hasher) {
483+
hasher.combine(native)
484+
}
485+
486+
@_transparent
487+
public func _rawHashValue(seed: Int) -> Int {
488+
return native._rawHashValue(seed: seed)
489+
}
480490
}
481491

482492
extension UInt8 {

Foundation/Calendar.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,8 +862,9 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi
862862
public func hash(into hasher: inout Hasher) {
863863
// We implement hash ourselves, because we need to make sure autoupdating calendars have the same hash
864864
if _autoupdating {
865-
hasher.combine(1 as Int8)
865+
hasher.combine(false)
866866
} else {
867+
hasher.combine(true)
867868
hasher.combine(_handle.map { $0 })
868869
}
869870
}

Foundation/Date.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ public struct Date : ReferenceConvertible, Comparable, Equatable {
136136
The distant past is in terms of centuries.
137137
*/
138138
public static let distantPast = Date(timeIntervalSinceReferenceDate: -63114076800.0)
139-
140-
public var hashValue: Int {
141-
return Int(bitPattern: __CFHashDouble(_time))
139+
140+
public func hash(into hasher: inout Hasher) {
141+
hasher.combine(_time)
142142
}
143143

144144
/// Compare two `Date` values.

Foundation/DateInterval.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,9 @@ public struct DateInterval : ReferenceConvertible, Comparable, Hashable {
150150
return false
151151
}
152152

153-
public var hashValue: Int {
154-
var buf: (UInt, UInt) = (UInt(start.timeIntervalSinceReferenceDate), UInt(end.timeIntervalSinceReferenceDate))
155-
return withUnsafeMutablePointer(to: &buf) { bufferPtr in
156-
let count = MemoryLayout<UInt>.size * 2
157-
return bufferPtr.withMemoryRebound(to: UInt8.self, capacity: count) { bufferBytes in
158-
return Int(bitPattern: CFHashBytes(bufferBytes, CFIndex(count)))
159-
}
160-
}
153+
public func hash(into hasher: inout Hasher) {
154+
hasher.combine(start)
155+
hasher.combine(duration)
161156
}
162157

163158
public static func ==(lhs: DateInterval, rhs: DateInterval) -> Bool {

Foundation/Decimal.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,12 @@ extension Decimal : Hashable, Comparable {
286286
return Int64(bitPattern: uint64Value)
287287
}
288288

289-
public var hashValue: Int {
290-
return Int(bitPattern: __CFHashDouble(doubleValue))
289+
public func hash(into hasher: inout Hasher) {
290+
// FIXME: This is a weak hash. We should rather normalize self to a
291+
// canonical member of the exact same equivalence relation that
292+
// NSDecimalCompare implements, then simply feed all components to the
293+
// hasher.
294+
hasher.combine(doubleValue)
291295
}
292296

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

Foundation/FileManager.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -828,11 +828,11 @@ public struct FileAttributeKey : RawRepresentable, Equatable, Hashable {
828828
public init(rawValue: String) {
829829
self.rawValue = rawValue
830830
}
831-
832-
public var hashValue: Int {
833-
return self.rawValue.hashValue
831+
832+
public func hash(into hasher: inout Hasher) {
833+
hasher.combine(rawValue)
834834
}
835-
835+
836836
public static func ==(_ lhs: FileAttributeKey, _ rhs: FileAttributeKey) -> Bool {
837837
return lhs.rawValue == rhs.rawValue
838838
}
@@ -873,8 +873,8 @@ public struct FileAttributeType : RawRepresentable, Equatable, Hashable {
873873
self.rawValue = rawValue
874874
}
875875

876-
public var hashValue: Int {
877-
return self.rawValue.hashValue
876+
public func hash(into hasher: inout Hasher) {
877+
hasher.combine(rawValue)
878878
}
879879

880880
public static func ==(_ lhs: FileAttributeType, _ rhs: FileAttributeType) -> Bool {

Foundation/HTTPCookie.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public struct HTTPCookiePropertyKey : RawRepresentable, Equatable, Hashable {
1818
self.rawValue = rawValue
1919
}
2020

21-
public var hashValue: Int {
22-
return self.rawValue.hashValue
21+
public func hash(into hasher: inout Hasher) {
22+
hasher.combine(rawValue)
2323
}
2424

2525
public static func ==(_ lhs: HTTPCookiePropertyKey, _ rhs: HTTPCookiePropertyKey) -> Bool {

Foundation/IndexPath.swift

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -675,23 +675,29 @@ public struct IndexPath : ReferenceConvertible, Equatable, Hashable, MutableColl
675675
}
676676
return .orderedSame
677677
}
678-
679-
public var hashValue: Int {
680-
func hashIndexes(first: Int, last: Int, count: Int) -> Int {
681-
let totalBits = MemoryLayout<Int>.size * 8
682-
let lengthBits = 8
683-
let firstIndexBits = (totalBits - lengthBits) / 2
684-
return count &+ (first << lengthBits) &+ (last << (lengthBits + firstIndexBits))
685-
}
686-
678+
679+
public func hash(into hasher: inout Hasher) {
680+
// Note: We compare all indices in ==, so for proper hashing, we must
681+
// also feed them all to the hasher.
682+
//
683+
// To ensure we have unique hash encodings in nested hashing contexts,
684+
// we combine the count of indices as well as the indices themselves.
685+
// (This matches what Array does.)
687686
switch _indexes {
688-
case .empty: return 0
689-
case .single(let index): return index.hashValue
690-
case .pair(let first, let second):
691-
return hashIndexes(first: first, last: second, count: 2)
692-
default:
693-
let cnt = _indexes.count
694-
return hashIndexes(first: _indexes[0], last: _indexes[cnt - 1], count: cnt)
687+
case .empty:
688+
hasher.combine(0)
689+
case let .single(index):
690+
hasher.combine(1)
691+
hasher.combine(index)
692+
case let .pair(first, second):
693+
hasher.combine(2)
694+
hasher.combine(first)
695+
hasher.combine(second)
696+
case let .array(indexes):
697+
hasher.combine(indexes.count)
698+
for index in indexes {
699+
hasher.combine(index)
700+
}
695701
}
696702
}
697703

Foundation/Locale.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,9 @@ public struct Locale : CustomStringConvertible, CustomDebugStringConvertible, Ha
423423

424424
public func hash(into hasher: inout Hasher) {
425425
if _autoupdating {
426-
hasher.combine(1 as Int8)
426+
hasher.combine(false)
427427
} else {
428+
hasher.combine(true)
428429
hasher.combine(_wrapped)
429430
}
430431
}

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 {

Foundation/NSAttributedString.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ extension NSAttributedString {
2121
self.rawValue = rawValue
2222
}
2323

24-
public var hashValue: Int {
25-
return rawValue.hashValue
24+
public func hash(into hasher: inout Hasher) {
25+
hasher.combine(rawValue)
26+
}
27+
28+
public static func ==(left: NSAttributedString.Key, right: NSAttributedString.Key) -> Bool {
29+
return left.rawValue == right.rawValue
2630
}
2731
}
2832
}

Foundation/NSCalendar.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ extension NSCalendar {
6565
public init(rawValue: String) {
6666
self.rawValue = rawValue
6767
}
68-
69-
public var hashValue: Int {
70-
return rawValue.hashValue
68+
69+
public func hash(into hasher: inout Hasher) {
70+
hasher.combine(rawValue)
7171
}
7272

7373
public static let gregorian = NSCalendar.Identifier("gregorian")

Foundation/NSCharacterSet.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ open class NSCharacterSet : NSObject, NSCopying, NSMutableCopying, NSCoding {
4646
}
4747

4848
open override var hash: Int {
49-
return Int(bitPattern: CFHash(_cfObject))
49+
return Int(bitPattern: _CFNonObjCHash(_cfObject))
5050
}
5151

5252
open override func isEqual(_ value: Any?) -> Bool {

Foundation/NSDecimalNumber.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public struct NSExceptionName : RawRepresentable, Equatable, Hashable {
1818
public init(rawValue: String) {
1919
self.rawValue = rawValue
2020
}
21-
22-
public var hashValue: Int {
23-
return self.rawValue.hashValue
21+
22+
public func hash(into hasher: inout Hasher) {
23+
hasher.combine(rawValue)
2424
}
2525

2626
public static func ==(_ lhs: NSExceptionName, _ rhs: NSExceptionName) -> Bool {

Foundation/NSError.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ extension __BridgedNSError where Self: RawRepresentable, Self.RawValue: FixedWid
456456
}
457457

458458
public var hashValue: Int { return _code }
459+
460+
public func hash(into hasher: inout Hasher) {
461+
hasher.combine(rawValue)
462+
}
459463
}
460464

461465
/// Describes a raw representable type that is bridged to a particular
@@ -534,7 +538,11 @@ public extension _BridgedStoredNSError {
534538
/// Implementation of Hashable for all _BridgedStoredNSErrors.
535539
public extension _BridgedStoredNSError {
536540
var hashValue: Int {
537-
return _nsError.hashValue
541+
return _nsError.hash
542+
}
543+
544+
func hash(into hasher: inout Hasher) {
545+
hasher.combine(_nsError)
538546
}
539547
}
540548

0 commit comments

Comments
 (0)