Skip to content

Commit b58a908

Browse files
authored
[cxx-interop] Add Hashable conformance to imported enums (#76940)
* [cxx-interop] Add Hashable conformance to imported enums Previously, imported enums only conformed to RawRepresentable and Equatable, so they could not be used as members of a Set or keys of a Dictionary. This patch adds Hashable conformance to give them that ability, as well as some test cases to clarify the expected behavior. Existing test cases are updated to reflect this new conformance. rdar://129713687
1 parent ddd5575 commit b58a908

13 files changed

+3167
-2447
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,8 @@ namespace {
16371637

16381638
synthesizer.makeStructRawValued(
16391639
structDecl, underlyingType,
1640-
{KnownProtocolKind::RawRepresentable, KnownProtocolKind::Equatable},
1640+
{KnownProtocolKind::RawRepresentable, KnownProtocolKind::Equatable,
1641+
KnownProtocolKind::Hashable},
16411642
options, /*setterAccess=*/AccessLevel::Public);
16421643

16431644
result = structDecl;

test/ClangImporter/Inputs/SwiftPrivateAttr.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,21 @@ struct __PrivS2 {
5656
var value: Int32
5757
}
5858
var __PrivAnonymousA: Int { get }
59-
struct E0 : Equatable, RawRepresentable {
59+
struct E0 : Hashable, Equatable, RawRepresentable {
6060
init(_ rawValue: UInt32)
6161
init(rawValue: UInt32)
6262
var rawValue: UInt32
6363
typealias RawValue = UInt32
6464
}
6565
var __E0PrivA: E0 { get }
66-
struct __PrivE1 : Equatable, RawRepresentable {
66+
struct __PrivE1 : Hashable, Equatable, RawRepresentable {
6767
init(_ rawValue: UInt32)
6868
init(rawValue: UInt32)
6969
var rawValue: UInt32
7070
typealias RawValue = UInt32
7171
}
7272
var __PrivE1A: __PrivE1 { get }
73-
struct __PrivE2 : Equatable, RawRepresentable {
73+
struct __PrivE2 : Hashable, Equatable, RawRepresentable {
7474
init(_ rawValue: UInt32)
7575
init(rawValue: UInt32)
7676
var rawValue: UInt32

test/Interop/Cxx/class/access-specifiers-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// CHECK-NEXT: struct PublicStruct {
1212
// CHECK-NEXT: init()
1313
// CHECK-NEXT: }
14-
// CHECK-NEXT: struct PublicEnum : Equatable, RawRepresentable {
14+
// CHECK-NEXT: struct PublicEnum : Hashable, Equatable, RawRepresentable {
1515
// CHECK-NEXT: init(_ rawValue: [[ENUM_UNDERLYING_TYPE:Int32|UInt32]])
1616
// CHECK-NEXT: init(rawValue: [[ENUM_UNDERLYING_TYPE]])
1717
// CHECK-NEXT: var rawValue: [[ENUM_UNDERLYING_TYPE]]

test/Interop/Cxx/class/inheritance/sub-types-module-interface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// CHECK-NEXT: case ecb
1111
// CHECK-NEXT: case ecc
1212
// CHECK-NEXT: }
13-
// CHECK-NEXT: struct Enum : Equatable, RawRepresentable {
13+
// CHECK-NEXT: struct Enum : Hashable, Equatable, RawRepresentable {
1414
// CHECK-NEXT: init(_ rawValue: {{UInt32|Int32}})
1515
// CHECK-NEXT: init(rawValue: {{UInt32|Int32}})
1616
// CHECK-NEXT: var rawValue: {{UInt32|Int32}}

test/Interop/Cxx/class/nested-records-module-interface.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// CHECK: }
1818

1919
// CHECK: struct U3 {
20-
// CHECK: struct E1 : Equatable, RawRepresentable {
20+
// CHECK: struct E1 : Hashable, Equatable, RawRepresentable {
2121
// CHECK: typealias RawValue = {{UInt32|Int32}}
2222
// CHECK: }
2323
// CHECK: }
@@ -29,7 +29,7 @@
2929

3030
// CHECK: struct S6 {
3131
// CHECK: init()
32-
// CHECK: struct E3 : Equatable, RawRepresentable {
32+
// CHECK: struct E3 : Hashable, Equatable, RawRepresentable {
3333
// CHECK: typealias RawValue = {{UInt32|Int32}}
3434
// CHECK: }
3535
// CHECK: }
@@ -50,7 +50,7 @@
5050

5151
// CHECK: struct S10 {
5252
// CHECK: struct U8 {
53-
// CHECK: struct E4 : Equatable, RawRepresentable {
53+
// CHECK: struct E4 : Hashable, Equatable, RawRepresentable {
5454
// CHECK: typealias RawValue = {{UInt32|Int32}}
5555
// CHECK: }
5656
// CHECK: }

test/Interop/Cxx/enum/bool-enums-module-interface.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
// TODO: these should be enums eventually (especially the enum class).
44

5-
// CHECK: struct Maybe : Equatable, RawRepresentable {
5+
// CHECK: struct Maybe : Hashable, Equatable, RawRepresentable {
66
// CHECK-NEXT: init(_ rawValue: Bool)
77
// CHECK-NEXT: init(rawValue: Bool)
88
// CHECK-NEXT: var rawValue: Bool
@@ -11,7 +11,7 @@
1111
// CHECK: var No: Maybe { get }
1212
// CHECK: var Yes: Maybe { get }
1313

14-
// CHECK: struct BinaryNumbers : Equatable, RawRepresentable {
14+
// CHECK: struct BinaryNumbers : Hashable, Equatable, RawRepresentable {
1515
// CHECK-NEXT: init(_ rawValue: Bool)
1616
// CHECK-NEXT: init(rawValue: Bool)
1717
// CHECK-NEXT: var rawValue: Bool
@@ -31,7 +31,7 @@
3131
// CHECK: struct WrapperStruct {
3232
// CHECK-NEXT: init()
3333
// TODO: where is "A" and "B"? They should be member variables.
34-
// CHECK-NEXT: struct InnerBoolEnum : Equatable, RawRepresentable {
34+
// CHECK-NEXT: struct InnerBoolEnum : Hashable, Equatable, RawRepresentable {
3535
// CHECK-NEXT: init(_ rawValue: Bool)
3636
// CHECK-NEXT: init(rawValue: Bool)
3737
// CHECK-NEXT: var rawValue: Bool
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-typecheck-verify-swift -I %S/Inputs -enable-experimental-cxx-interop
2+
3+
import TypedUntypedEnums
4+
5+
let _: Set = [kBlue, kGreen] // construct a set from blue and green
6+
let _: Set = [kBlue, kBlue] // construct a valid set from two blues
7+
8+
// Not allowed to mix and match elements of a set.
9+
let _: Set = [kBlue, kTwo] // expected-error {{conflicting arguments to generic parameter 'Element' ('Int' vs. 'Color')}}
10+
let _: Set = [kBlue, Pet.dogcow] // expected-error {{conflicting arguments to generic parameter 'Element' ('Pet' vs. 'Color')}}
11+
12+
let s: Set<Pet> = [] // construct an empty, type-annotated set
13+
let _ = s.contains(Pet.goat) // query the empty set using a key
14+
let _ = s.contains(kTwo) // expected-error {{cannot convert value of type 'Int' to expected argument type 'Pet'}}
15+
16+
// Untyped enums can be used interchangeably with integers
17+
let _: Set = [kFour, kTwo] // construct a set from untyped enum
18+
let _: Set = [kFour, kTwo, 0] // construct a set that mixes untyped enums and integers
19+
20+
let _ = [Pet.goat: "baa", Pet.dogcow: "moo"] // dictionaries are fine too
21+
let _: [AnyHashable: String] = [Pet.goat: "baa", kFour: "meow"] // even heterogeneous ones
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// RUN: %target-run-simple-swift(-cxx-interoperability-mode=default -Xfrontend -disable-availability-checking -I %S/Inputs)
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
import TypedUntypedEnums
6+
7+
// A helper for hashing Hasable values.
8+
func getHash<H>(_ x: H) -> Int where H: Hashable {
9+
var h = Hasher()
10+
h.combine(x)
11+
return h.finalize()
12+
}
13+
14+
let Colors = [kRed, kBlue, kGreen, kYellow]
15+
let Numbers = [kOne, kTwo, kThree, kFour]
16+
let Pets = [Pet.goat, Pet.cat, Pet.dogcow, Pet.rabbit]
17+
18+
var HashableEnumsTestSuite = TestSuite("Enums are hashable")
19+
20+
HashableEnumsTestSuite.test("Hashes preserve equality") {
21+
for m in 0..<Colors.count {
22+
for n in 0..<Colors.count {
23+
if m == n {
24+
expectEqual(getHash(Colors[m]), getHash(Colors[n]))
25+
} else {
26+
expectNotEqual(getHash(Colors[m]), getHash(Colors[n]))
27+
}
28+
}
29+
}
30+
31+
for m in 0..<Numbers.count {
32+
for n in 0..<Numbers.count {
33+
if m == n {
34+
expectEqual(getHash(Numbers[m]), getHash(Numbers[n]))
35+
} else {
36+
expectNotEqual(getHash(Numbers[m]), getHash(Numbers[n]))
37+
}
38+
}
39+
}
40+
41+
for m in 0..<Pets.count {
42+
for n in 0..<Pets.count {
43+
if m == n {
44+
expectEqual(getHash(Pets[m]), getHash(Pets[n]))
45+
} else {
46+
expectNotEqual(getHash(Pets[m]), getHash(Pets[n]))
47+
}
48+
}
49+
}
50+
}
51+
52+
HashableEnumsTestSuite.test("Untyped enums hash using underlying value") {
53+
for m in 1...4 {
54+
for n in 1...Numbers.count {
55+
let number = Numbers[n - 1]
56+
if m == n {
57+
expectEqual(getHash(m), getHash(number))
58+
} else {
59+
expectNotEqual(getHash(m), getHash(number))
60+
}
61+
}
62+
}
63+
}
64+
65+
HashableEnumsTestSuite.test("Typed enums and class enums hash using other info") {
66+
// The raw values of these enum members are known:
67+
expectEqual(kRed.rawValue, 0)
68+
expectEqual(kYellow.rawValue, 10)
69+
expectEqual(Pet.goat.rawValue, 5)
70+
expectEqual(Pet.cat.rawValue, 15)
71+
72+
// But the Hashable implementation uses more than the raw value to compute the hash:
73+
expectNotEqual(getHash(kRed), getHash(0))
74+
expectNotEqual(getHash(kYellow), getHash(10))
75+
expectNotEqual(getHash(Pet.goat), getHash(5))
76+
expectNotEqual(getHash(Pet.cat), getHash(15))
77+
}
78+
79+
HashableEnumsTestSuite.test("Sets work as expected") {
80+
let s: Set = [kRed, kBlue, kRed, kRed]
81+
assert(s.contains(kRed))
82+
assert(s.contains(kBlue))
83+
assert(!s.contains(kGreen))
84+
assert(s.count == 2) // kRed should have been deduplicated
85+
}
86+
87+
runAllTests()

test/Interop/Cxx/enum/nested-enums-module-interface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %target-swift-ide-test -print-module -module-to-print=NestedEnums -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s
22

33
// CHECK: enum ns {
4-
// CHECK: struct EnumInNS : Equatable, RawRepresentable {
4+
// CHECK: struct EnumInNS : Hashable, Equatable, RawRepresentable {
55
// CHECK: }
66
// CHECK-NEXT: static var kA: ns.EnumInNS { get }
77
// CHECK-NEXT: static var kB: ns.EnumInNS { get }
@@ -10,7 +10,7 @@
1010
// CHECK: case scopeB
1111
// CHECK-NEXT: }
1212
// CHECK-NEXT: enum nestedNS {
13-
// CHECK-NEXT: struct EnumInNestedNS : Equatable, RawRepresentable {
13+
// CHECK-NEXT: struct EnumInNestedNS : Hashable, Equatable, RawRepresentable {
1414
// CHECK: }
1515
// CHECK-NEXT: static var kNestedA: ns.nestedNS.EnumInNestedNS { get }
1616
// CHECK-NEXT: static var kNestedB: ns.nestedNS.EnumInNestedNS { get }

0 commit comments

Comments
 (0)