Skip to content

Commit eb6c665

Browse files
authored
Merge pull request #25928 from lorentey/RawRepresentable-hashing-5.1
[5.1][stdlib] RawRepresentable: revert to default _rawHashValue(seed:)
2 parents faf5be8 + bff21f3 commit eb6c665

File tree

2 files changed

+77
-35
lines changed

2 files changed

+77
-35
lines changed

stdlib/public/core/CompilerProtocols.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,21 @@ extension RawRepresentable where RawValue: Hashable, Self: Hashable {
195195

196196
@inlinable // trivial
197197
public func _rawHashValue(seed: Int) -> Int {
198-
return rawValue._rawHashValue(seed: seed)
198+
// In 5.0, this used to return rawValue._rawHashValue(seed: seed). This was
199+
// slightly faster, but it interfered with conforming types' ability to
200+
// customize their hashing. The current definition is equivalent to the
201+
// default implementation; however, we need to keep the definition to remain
202+
// ABI compatible with code compiled on 5.0.
203+
//
204+
// Note that unless a type provides a custom hash(into:) implementation,
205+
// this new version returns the same values as the original 5.0 definition,
206+
// so code that used to work in 5.0 remains working whether or not the
207+
// original definition was inlined.
208+
//
209+
// See https://bugs.swift.org/browse/SR-10734
210+
var hasher = Hasher(_seed: seed)
211+
self.hash(into: &hasher)
212+
return hasher._finalize()
199213
}
200214
}
201215

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1-
// RUN: %target-run-simple-swift | %FileCheck %s
1+
// RUN: %target-run-simple-swift
22
// REQUIRES: executable_test
33

4-
// RawRepresentable is not Equatable itself, but it does provide a generic
5-
// implementation of == based on rawValues. This gets picked up as the
6-
// implementation of Equatable.== when a concrete RawRepresentable type conforms
7-
// to Equatable without providing its own implementation.
8-
//
9-
// However, RawRepresentable used to not provide equivalent implementations for
10-
// hashing, allowing the compiler to synthesized hashing as usual, based on the
11-
// actual contents of the type rather than its rawValue. Thus, the definitions
12-
// of equality and hashing may not actually match, leading to broken hashes.
13-
//
14-
// The difference between rawValue and the actual contents is subtle, and it
15-
// only causes problems in custom RawRepresentable implementations where the
16-
// rawValue isn't actually the storage representation, like the weird struct
17-
// below.
18-
//
19-
// rdar://problem/45308741
4+
import StdlibUnittest
5+
6+
let suite = TestSuite("RawRepresentable")
7+
8+
extension Hasher {
9+
static func hash<H: Hashable>(_ value: H) -> Int {
10+
var hasher = Hasher()
11+
hasher.combine(value)
12+
return hasher.finalize()
13+
}
14+
}
15+
16+
2017

2118
struct TrickyRawRepresentable: RawRepresentable, Hashable {
2219
var value: [Unicode.Scalar]
@@ -30,27 +27,58 @@ struct TrickyRawRepresentable: RawRepresentable, Hashable {
3027
}
3128
}
3229

33-
let s1 = TrickyRawRepresentable(rawValue: "café")!
34-
let s2 = TrickyRawRepresentable(rawValue: "cafe\u{301}")!
30+
suite.test("Tricky hashing") {
31+
// RawRepresentable is not Equatable itself, but it does provide a generic
32+
// implementation of == based on rawValue. This gets picked up as the
33+
// implementation of Equatable.== when a concrete RawRepresentable type
34+
// conforms to Equatable without providing its own implementation.
35+
//
36+
// However, RawRepresentable used to not provide equivalent implementations
37+
// for hashing, allowing the compiler to synthesize hashing as usual, based on
38+
// the actual contents of the type rather than its rawValue. Thus, the
39+
// definitions of equality and hashing did not actually match in some cases,
40+
// leading to broken behavior.
41+
//
42+
// The difference between rawValue and the actual contents is subtle, and it
43+
// only causes problems in custom RawRepresentable implementations where the
44+
// rawValue isn't actually the storage representation, like the weird struct
45+
// above.
46+
//
47+
// rdar://problem/45308741
3548

36-
// CHECK: s1 == s2: true
37-
print("s1 == s2: \(s1 == s2)")
49+
let s1 = TrickyRawRepresentable(rawValue: "café")!
50+
let s2 = TrickyRawRepresentable(rawValue: "cafe\u{301}")!
3851

39-
// CHECK: hashValue matches: true
40-
print("hashValue matches: \(s1.hashValue == s2.hashValue)")
52+
expectEqual(s1, s2)
53+
expectEqual(s1.hashValue, s2.hashValue)
54+
expectEqual(Hasher.hash(s1), Hasher.hash(s2))
55+
expectEqual(s1._rawHashValue(seed: 42), s2._rawHashValue(seed: 42))
56+
}
4157

42-
extension Hasher {
43-
static func hash<H: Hashable>(_ value: H) -> Int {
44-
var hasher = Hasher()
45-
hasher.combine(value)
46-
return hasher.finalize()
58+
struct CustomRawRepresentable: RawRepresentable, Hashable {
59+
var rawValue: Int
60+
61+
init?(rawValue: Int) {
62+
self.rawValue = rawValue
63+
}
64+
65+
func hash(into hasher: inout Hasher) {
66+
hasher.combine(23)
4767
}
4868
}
4969

50-
// CHECK: hash(into:) matches: true
51-
print("hash(into:) matches: \(Hasher.hash(s1) == Hasher.hash(s2))")
70+
suite.test("Custom hashing") {
71+
// In 5.0, RawRepresentable had a bogus default implementation for
72+
// _rawHashValue(seed:) that interfered with custom hashing for
73+
// RawRepresentable types. Adding a custom hash(into:) implementation should
74+
// always be enough to customize hashing.
75+
//
76+
// See https://bugs.swift.org/browse/SR-10734
77+
78+
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
79+
let r = CustomRawRepresentable(rawValue: 42)!
80+
expectEqual(Hasher.hash(r), Hasher.hash(23))
81+
}
82+
}
5283

53-
// CHECK: _rawHashValue(seed:) matches: true
54-
let r1 = s1._rawHashValue(seed: 42)
55-
let r2 = s2._rawHashValue(seed: 42)
56-
print("_rawHashValue(seed:) matches: \(r1 == r2)")
84+
runAllTests()

0 commit comments

Comments
 (0)