Skip to content

Commit 67ff8dc

Browse files
authored
Merge pull request #23832 from lorentey/foundation-hashing
[Foundation] Modernize hashing in Foundation's Swift-only types
2 parents 1f0af4e + cbb96b3 commit 67ff8dc

31 files changed

+485
-159
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestAffineTransform.swift

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -315,58 +315,77 @@ class TestAffineTransform : TestAffineTransformSuper {
315315
checkPointTransformation(rotateAboutCenter, point: center, expectedPoint: center)
316316
}
317317

318-
func test_hashing_identity() {
319-
let ref = NSAffineTransform()
320-
let val = AffineTransform.identity
321-
expectEqual(ref.hashValue, val.hashValue)
322-
}
323-
324-
func test_hashing_values() {
318+
func test_hashing() {
325319
// the transforms are made up and the values don't matter
326-
let values = [
327-
AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7),
328-
AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33),
329-
AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2),
330-
AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99),
331-
AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44)
332-
]
333-
for val in values {
334-
let ref = val as NSAffineTransform
335-
expectEqual(ref.hashValue, val.hashValue)
320+
let a = AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7)
321+
let b = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33)
322+
let c = AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2)
323+
let d = AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99)
324+
let e = AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44)
325+
326+
// Samples testing that every component is properly hashed
327+
let x1 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0)
328+
let x2 = AffineTransform(m11: 1.5, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0)
329+
let x3 = AffineTransform(m11: 1.0, m12: 2.5, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.0)
330+
let x4 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.5, m22: 4.0, tX: 5.0, tY: 6.0)
331+
let x5 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.5, tX: 5.0, tY: 6.0)
332+
let x6 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.5, tY: 6.0)
333+
let x7 = AffineTransform(m11: 1.0, m12: 2.0, m21: 3.0, m22: 4.0, tX: 5.0, tY: 6.5)
334+
335+
@inline(never)
336+
func bridged(_ t: AffineTransform) -> NSAffineTransform {
337+
return t as NSAffineTransform
336338
}
337-
}
338339

339-
func test_AnyHashableContainingAffineTransform() {
340-
let values: [AffineTransform] = [
341-
AffineTransform.identity,
342-
AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33),
343-
AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33)
340+
let values: [[AffineTransform]] = [
341+
[AffineTransform.identity, NSAffineTransform() as AffineTransform],
342+
[a, bridged(a) as AffineTransform],
343+
[b, bridged(b) as AffineTransform],
344+
[c, bridged(c) as AffineTransform],
345+
[d, bridged(d) as AffineTransform],
346+
[e, bridged(e) as AffineTransform],
347+
[x1], [x2], [x3], [x4], [x5], [x6], [x7]
344348
]
345-
let anyHashables = values.map(AnyHashable.init)
346-
expectEqual(AffineTransform.self, type(of: anyHashables[0].base))
347-
expectEqual(AffineTransform.self, type(of: anyHashables[1].base))
348-
expectEqual(AffineTransform.self, type(of: anyHashables[2].base))
349-
expectNotEqual(anyHashables[0], anyHashables[1])
350-
expectEqual(anyHashables[1], anyHashables[2])
349+
checkHashableGroups(values)
351350
}
352351

353-
func test_AnyHashableCreatedFromNSAffineTransform() {
352+
func test_AnyHashable() {
354353
func makeNSAffineTransform(rotatedByDegrees angle: CGFloat) -> NSAffineTransform {
355354
let result = NSAffineTransform()
356355
result.rotate(byDegrees: angle)
357356
return result
358357
}
359-
let values: [NSAffineTransform] = [
360-
makeNSAffineTransform(rotatedByDegrees: 0),
361-
makeNSAffineTransform(rotatedByDegrees: 10),
362-
makeNSAffineTransform(rotatedByDegrees: 10),
358+
359+
let s1 = AffineTransform.identity
360+
let s2 = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33)
361+
let s3 = AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33)
362+
let s4 = makeNSAffineTransform(rotatedByDegrees: 10) as AffineTransform
363+
let s5 = makeNSAffineTransform(rotatedByDegrees: 10) as AffineTransform
364+
365+
let c1 = NSAffineTransform(transform: s1)
366+
let c2 = NSAffineTransform(transform: s2)
367+
let c3 = NSAffineTransform(transform: s3)
368+
let c4 = makeNSAffineTransform(rotatedByDegrees: 10)
369+
let c5 = makeNSAffineTransform(rotatedByDegrees: 10)
370+
371+
let groups: [[AnyHashable]] = [
372+
[s1, c1],
373+
[s2, c2, s3, c3],
374+
[s4, c4, s5, c5]
363375
]
364-
let anyHashables = values.map(AnyHashable.init)
365-
expectEqual(AffineTransform.self, type(of: anyHashables[0].base))
366-
expectEqual(AffineTransform.self, type(of: anyHashables[1].base))
367-
expectEqual(AffineTransform.self, type(of: anyHashables[2].base))
368-
expectNotEqual(anyHashables[0], anyHashables[1])
369-
expectEqual(anyHashables[1], anyHashables[2])
376+
checkHashableGroups(groups)
377+
378+
expectEqual(AffineTransform.self, type(of: (s1 as AnyHashable).base))
379+
expectEqual(AffineTransform.self, type(of: (s2 as AnyHashable).base))
380+
expectEqual(AffineTransform.self, type(of: (s3 as AnyHashable).base))
381+
expectEqual(AffineTransform.self, type(of: (s4 as AnyHashable).base))
382+
expectEqual(AffineTransform.self, type(of: (s5 as AnyHashable).base))
383+
384+
expectEqual(AffineTransform.self, type(of: (c1 as AnyHashable).base))
385+
expectEqual(AffineTransform.self, type(of: (c2 as AnyHashable).base))
386+
expectEqual(AffineTransform.self, type(of: (c3 as AnyHashable).base))
387+
expectEqual(AffineTransform.self, type(of: (c4 as AnyHashable).base))
388+
expectEqual(AffineTransform.self, type(of: (c5 as AnyHashable).base))
370389
}
371390

372391
func test_unconditionallyBridgeFromObjectiveC() {
@@ -400,10 +419,8 @@ AffineTransformTests.test("test_ScalingTranslation") { TestAffineTransform().tes
400419
AffineTransformTests.test("test_AppendTransform") { TestAffineTransform().test_AppendTransform() }
401420
AffineTransformTests.test("test_PrependTransform") { TestAffineTransform().test_PrependTransform() }
402421
AffineTransformTests.test("test_TransformComposition") { TestAffineTransform().test_TransformComposition() }
403-
AffineTransformTests.test("test_hashing_identity") { TestAffineTransform().test_hashing_identity() }
404-
AffineTransformTests.test("test_hashing_values") { TestAffineTransform().test_hashing_values() }
405-
AffineTransformTests.test("test_AnyHashableContainingAffineTransform") { TestAffineTransform().test_AnyHashableContainingAffineTransform() }
406-
AffineTransformTests.test("test_AnyHashableCreatedFromNSAffineTransform") { TestAffineTransform().test_AnyHashableCreatedFromNSAffineTransform() }
422+
AffineTransformTests.test("test_hashing") { TestAffineTransform().test_hashing() }
423+
AffineTransformTests.test("test_AnyHashable") { TestAffineTransform().test_AnyHashable() }
407424
AffineTransformTests.test("test_unconditionallyBridgeFromObjectiveC") { TestAffineTransform().test_unconditionallyBridgeFromObjectiveC() }
408425
AffineTransformTests.test("test_rotation_compose") { TestAffineTransform().test_rotation_compose() }
409426
runAllTests()

Darwin/Foundation-swiftoverlay-Tests/TestCalendar.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,27 @@ class TestCalendar : TestCalendarSuper {
109109

110110
current2.locale = Locale(identifier: "MyMadeUpLocale")
111111
expectNotEqual(current, current2)
112+
}
113+
114+
func test_hash() {
115+
let calendars: [Calendar] = [
116+
Calendar.autoupdatingCurrent,
117+
Calendar(identifier: .buddhist),
118+
Calendar(identifier: .gregorian),
119+
Calendar(identifier: .islamic),
120+
Calendar(identifier: .iso8601),
121+
]
122+
checkHashable(calendars, equalityOracle: { $0 == $1 })
123+
124+
// autoupdating calendar isn't equal to the current, even though it's
125+
// likely to be the same.
126+
let calendars2: [Calendar] = [
127+
Calendar.autoupdatingCurrent,
128+
Calendar.current,
129+
]
130+
checkHashable(calendars2, equalityOracle: { $0 == $1 })
112131
}
113-
132+
114133
func test_properties() {
115134
// Mainly we want to just make sure these go through to the NSCalendar implementation at this point.
116135
if #available(iOS 8.0, OSX 10.7, *) {
@@ -299,6 +318,7 @@ var CalendarTests = TestSuite("TestCalendar")
299318
CalendarTests.test("test_copyOnWrite") { TestCalendar().test_copyOnWrite() }
300319
CalendarTests.test("test_bridgingAutoupdating") { TestCalendar().test_bridgingAutoupdating() }
301320
CalendarTests.test("test_equality") { TestCalendar().test_equality() }
321+
CalendarTests.test("test_hash") { TestCalendar().test_hash() }
302322
CalendarTests.test("test_properties") { TestCalendar().test_properties() }
303323
CalendarTests.test("test_AnyHashableContainingCalendar") { TestCalendar().test_AnyHashableContainingCalendar() }
304324
CalendarTests.test("test_AnyHashableCreatedFromNSCalendar") { TestCalendar().test_AnyHashableCreatedFromNSCalendar() }

Darwin/Foundation-swiftoverlay-Tests/TestCharacterSet.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ class TestCharacterSet : TestCharacterSetSuper {
182182
expectTrue(actualClassForCoder == expectedImmutable || actualClassForCoder == expectedMutable)
183183
}
184184

185+
func test_hashing() {
186+
let a = CharacterSet(charactersIn: "ABC")
187+
let b = CharacterSet(charactersIn: "CBA")
188+
let c = CharacterSet(charactersIn: "bad")
189+
let d = CharacterSet(charactersIn: "abd")
190+
let e = CharacterSet.capitalizedLetters
191+
let f = CharacterSet.lowercaseLetters
192+
checkHashableGroups(
193+
[[a, b], [c, d], [e], [f]],
194+
// FIXME: CharacterSet delegates equality and hashing to
195+
// CFCharacterSet, which uses unseeded hashing, so it's not
196+
// complete.
197+
allowIncompleteHashing: true)
198+
}
199+
185200
func test_AnyHashableContainingCharacterSet() {
186201
let values: [CharacterSet] = [
187202
CharacterSet(charactersIn: "ABC"),
@@ -327,6 +342,7 @@ CharacterSetTests.test("testRanges") { TestCharacterSet().testRanges() }
327342
CharacterSetTests.test("testInsertAndRemove") { TestCharacterSet().testInsertAndRemove() }
328343
CharacterSetTests.test("testBasics") { TestCharacterSet().testBasics() }
329344
CharacterSetTests.test("test_classForCoder") { TestCharacterSet().test_classForCoder() }
345+
CharacterSetTests.test("test_hashing") { TestCharacterSet().test_hashing() }
330346
CharacterSetTests.test("test_AnyHashableContainingCharacterSet") { TestCharacterSet().test_AnyHashableContainingCharacterSet() }
331347
CharacterSetTests.test("test_AnyHashableCreatedFromNSCharacterSet") { TestCharacterSet().test_AnyHashableCreatedFromNSCharacterSet() }
332348
CharacterSetTests.test("test_superSet") { TestCharacterSet().test_superSet() }

Darwin/Foundation-swiftoverlay-Tests/TestDate.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,19 @@ class TestDate : TestDateSuper {
130130
expectEqual(1999, dc2.year)
131131
}
132132

133+
func test_DateHashing() {
134+
let values: [Date] = [
135+
dateWithString("2010-05-17 14:49:47 -0700"),
136+
dateWithString("2011-05-17 14:49:47 -0700"),
137+
dateWithString("2010-06-17 14:49:47 -0700"),
138+
dateWithString("2010-05-18 14:49:47 -0700"),
139+
dateWithString("2010-05-17 15:49:47 -0700"),
140+
dateWithString("2010-05-17 14:50:47 -0700"),
141+
dateWithString("2010-05-17 14:49:48 -0700"),
142+
]
143+
checkHashable(values, equalityOracle: { $0 == $1 })
144+
}
145+
133146
func test_AnyHashableContainingDate() {
134147
let values: [Date] = [
135148
dateWithString("2016-05-17 14:49:47 -0700"),
@@ -206,6 +219,7 @@ DateTests.test("testDistantFuture") { TestDate().testDistantFuture() }
206219
DateTests.test("testEquality") { TestDate().testEquality() }
207220
DateTests.test("testTimeIntervalSinceDate") { TestDate().testTimeIntervalSinceDate() }
208221
DateTests.test("testDateComponents") { TestDate().testDateComponents() }
222+
DateTests.test("test_DateHashing") { TestDate().test_DateHashing() }
209223
DateTests.test("test_AnyHashableContainingDate") { TestDate().test_AnyHashableContainingDate() }
210224
DateTests.test("test_AnyHashableCreatedFromNSDate") { TestDate().test_AnyHashableCreatedFromNSDate() }
211225
DateTests.test("test_AnyHashableContainingDateComponents") { TestDate().test_AnyHashableContainingDateComponents() }

Darwin/Foundation-swiftoverlay-Tests/TestDateInterval.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,36 @@ class TestDateInterval : TestDateIntervalSuper {
6565
}
6666
}
6767

68+
func test_hashing() {
69+
guard #available(iOS 10.10, OSX 10.12, tvOS 10.0, watchOS 3.0, *) else { return }
70+
71+
let start1a = dateWithString("2019-04-04 17:09:23 -0700")
72+
let start1b = dateWithString("2019-04-04 17:09:23 -0700")
73+
let start2a = Date(timeIntervalSinceReferenceDate: start1a.timeIntervalSinceReferenceDate.nextUp)
74+
let start2b = Date(timeIntervalSinceReferenceDate: start1a.timeIntervalSinceReferenceDate.nextUp)
75+
let duration1 = 1800.0
76+
let duration2 = duration1.nextUp
77+
let intervals: [[DateInterval]] = [
78+
[
79+
DateInterval(start: start1a, duration: duration1),
80+
DateInterval(start: start1b, duration: duration1),
81+
],
82+
[
83+
DateInterval(start: start1a, duration: duration2),
84+
DateInterval(start: start1b, duration: duration2),
85+
],
86+
[
87+
DateInterval(start: start2a, duration: duration1),
88+
DateInterval(start: start2b, duration: duration1),
89+
],
90+
[
91+
DateInterval(start: start2a, duration: duration2),
92+
DateInterval(start: start2b, duration: duration2),
93+
],
94+
]
95+
checkHashableGroups(intervals)
96+
}
97+
6898
func test_checkIntersection() {
6999
if #available(iOS 10.10, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
70100
let start1 = dateWithString("2010-05-17 14:49:47 -0700")
@@ -171,6 +201,7 @@ class TestDateInterval : TestDateIntervalSuper {
171201
var DateIntervalTests = TestSuite("TestDateInterval")
172202
DateIntervalTests.test("test_compareDateIntervals") { TestDateInterval().test_compareDateIntervals() }
173203
DateIntervalTests.test("test_isEqualToDateInterval") { TestDateInterval().test_isEqualToDateInterval() }
204+
DateIntervalTests.test("test_hashing") { TestDateInterval().test_hashing() }
174205
DateIntervalTests.test("test_checkIntersection") { TestDateInterval().test_checkIntersection() }
175206
DateIntervalTests.test("test_validIntersections") { TestDateInterval().test_validIntersections() }
176207
DateIntervalTests.test("test_AnyHashableContainingDateInterval") { TestDateInterval().test_AnyHashableContainingDateInterval() }

Darwin/Foundation-swiftoverlay-Tests/TestIndexPath.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,24 @@ class TestIndexPath: TestIndexPathSuper {
222222
}
223223

224224
func testHashing() {
225-
let ip1: IndexPath = [5, 1]
226-
let ip2: IndexPath = [1, 1, 1]
227-
228-
expectNotEqual(ip1.hashValue, ip2.hashValue)
225+
let samples: [IndexPath] = [
226+
[],
227+
[1],
228+
[2],
229+
[Int.max],
230+
[1, 1],
231+
[2, 1],
232+
[1, 2],
233+
[1, 1, 1],
234+
[2, 1, 1],
235+
[1, 2, 1],
236+
[1, 1, 2],
237+
[Int.max, Int.max, Int.max],
238+
]
239+
checkHashable(samples, equalityOracle: { $0 == $1 })
229240

230-
IndexPath(indexes: [Int.max >> 8, 2, Int.max >> 36]).hashValue // this should not cause an overflow crash
241+
// this should not cause an overflow crash
242+
_ = IndexPath(indexes: [Int.max >> 8, 2, Int.max >> 36]).hashValue
231243
}
232244

233245
func testEquality() {

0 commit comments

Comments
 (0)