1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// UNSUPPORTED: freestanding
import StdlibUnittest
let suite = TestSuite("RawRepresentable")
extension Hasher {
static func hash<H: Hashable>(_ value: H) -> Int {
var hasher = Hasher()
hasher.combine(value)
return hasher.finalize()
}
}
struct TrickyRawRepresentable: RawRepresentable, Hashable {
var value: [Unicode.Scalar]
var rawValue: String {
return String(String.UnicodeScalarView(value))
}
init?(rawValue: String) {
self.value = Array(rawValue.unicodeScalars)
}
}
suite.test("Tricky hashing") {
// RawRepresentable is not Equatable itself, but it does provide a generic
// implementation of == based on rawValue. This gets picked up as the
// implementation of Equatable.== when a concrete RawRepresentable type
// conforms to Equatable without providing its own implementation.
//
// However, RawRepresentable used to not provide equivalent implementations
// for hashing, allowing the compiler to synthesize hashing as usual, based on
// the actual contents of the type rather than its rawValue. Thus, the
// definitions of equality and hashing did not actually match in some cases,
// leading to broken behavior.
//
// The difference between rawValue and the actual contents is subtle, and it
// only causes problems in custom RawRepresentable implementations where the
// rawValue isn't actually the storage representation, like the weird struct
// above.
//
// rdar://problem/45308741
let s1 = TrickyRawRepresentable(rawValue: "café")!
let s2 = TrickyRawRepresentable(rawValue: "cafe\u{301}")!
expectEqual(s1, s2)
expectEqual(s1.hashValue, s2.hashValue)
expectEqual(Hasher.hash(s1), Hasher.hash(s2))
expectEqual(s1._rawHashValue(seed: 42), s2._rawHashValue(seed: 42))
}
struct CustomRawRepresentable: RawRepresentable, Hashable {
var rawValue: Int
init?(rawValue: Int) {
self.rawValue = rawValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(23)
}
}
suite.test("_rawHashValue forwarding") {
// In 5.0, RawRepresentable had a bogus default implementation for
// _rawHashValue(seed:) that interfered with custom hashing for
// RawRepresentable types. Adding a custom hash(into:) implementation should
// always be enough to customize hashing.
//
// See https://github.com/apple/swift/issues/53126.
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) {
let r = CustomRawRepresentable(rawValue: 42)!
expectEqual(Hasher.hash(r), Hasher.hash(23))
}
}
struct Bogus: Hashable {
var hashValue: Int { 42 }
static func ==(left: Self, right: Self) -> Bool { true }
}
struct CustomRawRepresentable2: RawRepresentable, Hashable {
var rawValue: Bogus
init?(rawValue: Bogus) {
self.rawValue = rawValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(23)
}
}
suite.test("hashValue forwarding") {
// In versions up to and including 5.5, RawRepresentable had a bogus default
// implementation for `hashValue` that forwarded directly to
// `rawValue.hashValue`, instead of `self.hash(into:)`. Adding a custom
// hash(into:) implementation should always be enough to customize hashing.
//
// See https://github.com/apple/swift/pull/39155
if #available(SwiftStdlib 5.6, *) {
let r = CustomRawRepresentable2(rawValue: Bogus())!
expectEqual(r.hashValue, 23.hashValue)
}
}
runAllTests()
|