Skip to content

Commit a062aa7

Browse files
committed
[Foundation] Measurement: Fix hashing
1 parent c05f33e commit a062aa7

File tree

2 files changed

+72
-11
lines changed

2 files changed

+72
-11
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestMeasurement.swift

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ class MyDimensionalUnit : Dimension {
4141
}
4242

4343
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
44-
class BugUnit : Unit {
44+
class CustomUnit : Unit {
4545
override init(symbol: String) {
46-
precondition(symbol == "bug")
4746
super.init(symbol: symbol)
4847
}
4948

5049
required init?(coder aDecoder: NSCoder) {
5150
super.init(coder: aDecoder)
5251
}
5352

54-
public static let bugs = BugUnit(symbol: "bug")
53+
public static let bugs = CustomUnit(symbol: "bug")
54+
public static let features = CustomUnit(symbol: "feature")
5555
}
5656

5757
@available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
@@ -66,9 +66,9 @@ class TestMeasurement : TestMeasurementSuper {
6666
expectEqual(6, m3.value)
6767
expectEqual(m1, m2)
6868

69-
let m10 = Measurement(value: 2, unit: BugUnit.bugs)
70-
let m11 = Measurement(value: 2, unit: BugUnit.bugs)
71-
let m12 = Measurement(value: 3, unit: BugUnit.bugs)
69+
let m10 = Measurement(value: 2, unit: CustomUnit.bugs)
70+
let m11 = Measurement(value: 2, unit: CustomUnit.bugs)
71+
let m12 = Measurement(value: 3, unit: CustomUnit.bugs)
7272

7373
expectEqual(m10, m11)
7474
expectNotEqual(m10, m12)
@@ -89,7 +89,7 @@ class TestMeasurement : TestMeasurementSuper {
8989

9090
// This correctly fails to build
9191

92-
// let m2 = Measurement(value: 1, unit: BugUnit.bugs)
92+
// let m2 = Measurement(value: 1, unit: CustomUnit.bugs)
9393
// m2.converted(to: MyDimensionalUnit.unitKiloA)
9494
}
9595

@@ -113,9 +113,9 @@ class TestMeasurement : TestMeasurementSuper {
113113
// Dynamically different dimensions
114114
expectEqual(Measurement(value: 1_001_000, unit: MyDimensionalUnit.unitA), oneMegaA + oneKiloA)
115115

116-
var bugCount = Measurement(value: 1, unit: BugUnit.bugs)
116+
var bugCount = Measurement(value: 1, unit: CustomUnit.bugs)
117117
expectEqual(bugCount.value, 1)
118-
bugCount = bugCount + Measurement(value: 4, unit: BugUnit.bugs)
118+
bugCount = bugCount + Measurement(value: 4, unit: CustomUnit.bugs)
119119
expectEqual(bugCount.value, 5)
120120
}
121121

@@ -154,6 +154,51 @@ class TestMeasurement : TestMeasurementSuper {
154154
expectTrue(fiveKM <= fiveThousandM)
155155
}
156156

157+
func testHashing() {
158+
let lengths: [[Measurement<UnitLength>]] = [
159+
[
160+
Measurement(value: 5, unit: UnitLength.kilometers),
161+
Measurement(value: 5000, unit: UnitLength.meters),
162+
Measurement(value: 5000, unit: UnitLength.meters),
163+
],
164+
[
165+
Measurement(value: 1, unit: UnitLength.kilometers),
166+
Measurement(value: 1000, unit: UnitLength.meters),
167+
],
168+
[
169+
Measurement(value: 1, unit: UnitLength.meters),
170+
Measurement(value: 1000, unit: UnitLength.millimeters),
171+
],
172+
]
173+
checkHashableGroups(lengths)
174+
175+
let durations: [[Measurement<UnitDuration>]] = [
176+
[
177+
Measurement(value: 3600, unit: UnitDuration.seconds),
178+
Measurement(value: 60, unit: UnitDuration.minutes),
179+
Measurement(value: 1, unit: UnitDuration.hours),
180+
],
181+
[
182+
Measurement(value: 1800, unit: UnitDuration.seconds),
183+
Measurement(value: 30, unit: UnitDuration.minutes),
184+
Measurement(value: 0.5, unit: UnitDuration.hours),
185+
]
186+
]
187+
checkHashableGroups(durations)
188+
189+
let custom: [Measurement<CustomUnit>] = [
190+
Measurement(value: 1, unit: CustomUnit.bugs),
191+
Measurement(value: 2, unit: CustomUnit.bugs),
192+
Measurement(value: 3, unit: CustomUnit.bugs),
193+
Measurement(value: 4, unit: CustomUnit.bugs),
194+
Measurement(value: 1, unit: CustomUnit.features),
195+
Measurement(value: 2, unit: CustomUnit.features),
196+
Measurement(value: 3, unit: CustomUnit.features),
197+
Measurement(value: 4, unit: CustomUnit.features),
198+
]
199+
checkHashable(custom, equalityOracle: { $0 == $1 })
200+
}
201+
157202
func test_AnyHashableContainingMeasurement() {
158203
let values: [Measurement<UnitLength>] = [
159204
Measurement(value: 100, unit: UnitLength.meters),
@@ -193,6 +238,7 @@ if #available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *) {
193238
MeasurementTests.test("testMeasurementFormatter") { TestMeasurement().testMeasurementFormatter() }
194239
MeasurementTests.test("testEquality") { TestMeasurement().testEquality() }
195240
MeasurementTests.test("testComparison") { TestMeasurement().testComparison() }
241+
MeasurementTests.test("testHashing") { TestMeasurement().testHashing() }
196242
MeasurementTests.test("test_AnyHashableContainingMeasurement") { TestMeasurement().test_AnyHashableContainingMeasurement() }
197243
MeasurementTests.test("test_AnyHashableCreatedFromNSMeasurement") { TestMeasurement().test_AnyHashableCreatedFromNSMeasurement() }
198244
runAllTests()

Darwin/Foundation-swiftoverlay/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 {

0 commit comments

Comments
 (0)