File: RawRepresentable-tricky-hashing.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (117 lines) | stat: -rw-r--r-- 3,458 bytes parent folder | download
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()