// RUN: %target-run-simple-swift // REQUIRES: executable_test // UNSUPPORTED: freestanding import StdlibUnittest let suite = TestSuite("RawRepresentable") extension Hasher { static func hash(_ 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()