Skip to content

Commit 5f39659

Browse files
committed
[test] Update enum Equatable/Hashable tests
1 parent 425de49 commit 5f39659

5 files changed

+350
-2
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Note that for the test to be effective, each of these structs must only have
2+
// its Equatable or Hashable conformance referenced /once/ in the primary file.
3+
struct FromOtherFile: Hashable {
4+
let v: String
5+
}
6+
struct AlsoFromOtherFile: Hashable {
7+
let v: Int
8+
}
9+
struct YetAnotherFromOtherFile: Hashable {
10+
let v: Float
11+
}

test/Sema/enum_equatable_hashable.swift

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,60 @@ enum Complex {
8989
if Complex.A(1) == .B { } // expected-error{{binary operator '==' cannot be applied to operands of type 'Complex' and '_'}}
9090
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
9191

92+
// Enums with equatable payloads are equatable if they explicitly conform.
93+
enum EnumWithEquatablePayload: Equatable {
94+
case A(Int)
95+
case B(String, Int)
96+
case C
97+
}
98+
99+
if EnumWithEquatablePayload.A(1) == .B("x", 1) { }
100+
if EnumWithEquatablePayload.A(1) == .C { }
101+
if EnumWithEquatablePayload.B("x", 1) == .C { }
102+
103+
// Enums with hashable payloads are hashable if they explicitly conform.
104+
enum EnumWithHashablePayload: Hashable {
105+
case A(Int)
106+
case B(String, Int)
107+
case C
108+
}
109+
110+
_ = EnumWithHashablePayload.A(1).hashValue
111+
_ = EnumWithHashablePayload.B("x", 1).hashValue
112+
_ = EnumWithHashablePayload.C.hashValue
113+
114+
// ...and they should also inherit equatability from Hashable.
115+
116+
if EnumWithHashablePayload.A(1) == .B("x", 1) { }
117+
if EnumWithHashablePayload.A(1) == .C { }
118+
if EnumWithHashablePayload.B("x", 1) == .C { }
119+
120+
// Enums with non-hashable payloads don't derive conformance.
121+
struct NotHashable {}
122+
enum EnumWithNonHashablePayload: Hashable { // expected-error 2 {{does not conform}}
123+
case A(NotHashable)
124+
}
125+
126+
// Enums should be able to derive conformances based on the conformances of
127+
// their generic arguments.
128+
enum GenericHashable<T: Hashable>: Hashable {
129+
case A(T)
130+
case B
131+
}
132+
if GenericHashable<String>.A("a") == .B { }
133+
var genericHashableHash: Int = GenericHashable<String>.A("a").hashValue
134+
135+
// But it should be an error if the generic argument doesn't have the necessary
136+
// constrants to satisfy the conditions for derivation.
137+
enum GenericNotHashable<T: Equatable>: Hashable { // expected-error 2 {{does not conform}}
138+
case A(T)
139+
case B
140+
}
141+
if GenericNotHashable<String>.A("a") == .B { }
142+
var genericNotHashableHash: Int = GenericNotHashable<String>.A("a").hashValue // expected-error {{value of type 'GenericNotHashable<String>' has no member 'hashValue'}}
143+
144+
// An enum with no cases should not derive conformance.
145+
enum NoCases: Hashable {} // expected-error 2 {{does not conform}}
92146

93147
// rdar://19773050
94148
private enum Bar<T> {
@@ -124,8 +178,14 @@ public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note{{non-mat
124178
return true
125179
}
126180

127-
// No explicit conformance and cannot be derived
128-
extension Complex : Hashable {} // expected-error 2 {{does not conform}}
181+
// No explicit conformance, but it can be derived.
182+
extension Complex : Hashable {}
183+
184+
// No explicit conformance and it cannot be derived.
185+
enum NotExplicitlyHashableAndCannotDerive { // expected-error 2 {{does not conform}}
186+
case A(NotHashable)
187+
}
188+
extension NotExplicitlyHashableAndCannotDerive : Hashable {}
129189

130190
// FIXME: Remove -verify-ignore-unknown.
131191
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// RUN: %target-run-simple-swift
2+
3+
// REQUIRES: executable-test
4+
5+
enum Token: Hashable {
6+
case string(String)
7+
case number(Int)
8+
case comma
9+
case colon
10+
}
11+
12+
guard Token.string("foo") == .string("foo") else { fatalError() }
13+
guard Token.string("foo") != .string("bar") else { fatalError() }
14+
guard Token.string("foo") != .number(5) else { fatalError() }
15+
guard Token.string("foo") != .comma else { fatalError() }
16+
guard Token.string("foo") != .colon else { fatalError() }
17+
guard Token.number(10) == .number(10) else { fatalError() }
18+
guard Token.number(10) != .number(20) else { fatalError() }
19+
guard Token.number(10) != .comma else { fatalError() }
20+
guard Token.number(10) != .colon else { fatalError() }
21+
guard Token.comma == .comma else { fatalError() }
22+
guard Token.comma != .colon else { fatalError() }
23+
guard Token.comma == .comma else { fatalError() }
24+
_ = Token.string("foo").hashValue
25+
_ = Token.number(5).hashValue
26+
_ = Token.comma.hashValue
27+
_ = Token.colon.hashValue
28+
29+
// Verify that the hash value also depends on the payload. This depends on
30+
// _mixInt not giving us a collision for the values we use below.
31+
struct FixedHashValue: Hashable {
32+
let hashValue: Int
33+
}
34+
enum Foo: Hashable {
35+
case a(FixedHashValue)
36+
}
37+
guard Foo.a(FixedHashValue(hashValue: 1)) != .a(FixedHashValue(hashValue: 2)) else { fatalError() }
38+
39+
// Try something a little more complex.
40+
enum Combo<T: Hashable, U: Hashable>: Hashable {
41+
case none
42+
case first(T)
43+
case second(U)
44+
case both(T, U)
45+
}
46+
47+
guard Combo<String, Int>.none == .none else { fatalError() }
48+
guard Combo<String, Int>.none != .first("foo") else { fatalError() }
49+
guard Combo<String, Int>.none != .second(5) else { fatalError() }
50+
guard Combo<String, Int>.none != .both("foo", 5) else { fatalError() }
51+
guard Combo<String, Int>.first("a") == .first("a") else { fatalError() }
52+
guard Combo<String, Int>.first("a") != .first("b") else { fatalError() }
53+
guard Combo<String, Int>.first("a") != .second(5) else { fatalError() }
54+
guard Combo<String, Int>.second(3) != .second(5) else { fatalError() }
55+
guard Combo<String, Int>.second(5) == .second(5) else { fatalError() }
56+
guard Combo<String, Int>.both("foo", 5) == .both("foo", 5) else { fatalError() }
57+
guard Combo<String, Int>.both("bar", 5) != .both("foo", 5) else { fatalError() }
58+
guard Combo<String, Int>.both("foo", 3) != .both("foo", 5) else { fatalError() }
59+
guard Combo<String, Int>.both("bar", 3) != .both("foo", 5) else { fatalError() }
60+
_ = Combo<String, Int>.none.hashValue
61+
_ = Combo<String, Int>.first("a").hashValue
62+
_ = Combo<String, Int>.second(5).hashValue
63+
_ = Combo<String, Int>.both("foo", 5).hashValue
64+
65+
// Make sure that if the user overrides the synthesized member, that one gets
66+
// used instead.
67+
enum Overrides: Hashable {
68+
case a(Int), b(String)
69+
var hashValue: Int { return 2 }
70+
static func == (lhs: Overrides, rhs: Overrides) -> Bool { return true }
71+
}
72+
guard Overrides.a(4) == .b("foo") else { fatalError() }
73+
guard Overrides.a(4).hashValue == 2 else { fatalError() }
74+
guard Overrides.b("foo").hashValue == 2 else { fatalError() }
75+
76+
// ...even in an extension.
77+
enum OverridesInExtension: Hashable {
78+
case a(Int), b(String)
79+
}
80+
extension OverridesInExtension {
81+
var hashValue: Int { return 2 }
82+
static func == (lhs: OverridesInExtension, rhs: OverridesInExtension) -> Bool { return true }
83+
}
84+
guard OverridesInExtension.a(4) == .b("foo") else { fatalError() }
85+
guard OverridesInExtension.a(4).hashValue == 2 else { fatalError() }
86+
guard OverridesInExtension.b("foo").hashValue == 2 else { fatalError() }
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: cp %s %t/main.swift
3+
// RUN: %target-swift-frontend -typecheck -verify -primary-file %t/main.swift %S/Inputs/struct_equatable_hashable_other.swift -verify-ignore-unknown
4+
5+
struct Point: Hashable {
6+
let x: Int
7+
let y: Int
8+
}
9+
10+
if Point(x: 1, y: 2) == Point(x: 2, y: 1) { }
11+
var pointHash: Int = Point(x: 3, y: 5).hashValue
12+
13+
struct Pair<T: Hashable>: Hashable {
14+
let first: T
15+
let second: T
16+
17+
func same() -> Bool {
18+
return first == second
19+
}
20+
}
21+
22+
let p1 = Pair(first: "a", second: "b")
23+
let p2 = Pair(first: "a", second: "c")
24+
if p1 == p2 { }
25+
var pairHash: Int = p1.hashValue
26+
27+
func localStruct() -> Bool {
28+
struct Local: Equatable {
29+
let v: Int
30+
}
31+
32+
return Local(v: 5) == Local(v: 4)
33+
}
34+
35+
struct CustomHashable: Hashable {
36+
let x: Int
37+
let y: Int
38+
39+
var hashValue: Int { return 0 }
40+
41+
static func ==(x: CustomHashable, y: CustomHashable) -> Bool { return true }
42+
}
43+
44+
if CustomHashable(x: 1, y: 2) == CustomHashable(x: 2, y: 3) { }
45+
var custHash: Int = CustomHashable(x: 1, y: 2).hashValue
46+
47+
// Check use of an struct's synthesized members before the struct is actually declared.
48+
struct UseStructBeforeDeclaration {
49+
let eqValue = StructToUseBeforeDeclaration(v: 4) == StructToUseBeforeDeclaration(v: 5)
50+
let hashValue = StructToUseBeforeDeclaration(v: 1).hashValue
51+
}
52+
struct StructToUseBeforeDeclaration: Hashable {
53+
let v: Int
54+
}
55+
56+
// Check structs from another file in the same module.
57+
if FromOtherFile(v: "a") == FromOtherFile(v: "b") {}
58+
let _: Int = FromOtherFile(v: "c").hashValue
59+
60+
func getFromOtherFile() -> AlsoFromOtherFile { return AlsoFromOtherFile(v: 4) }
61+
if AlsoFromOtherFile(v: 3) == getFromOtherFile() {}
62+
63+
// FIXME: This should work.
64+
func overloadFromOtherFile() -> YetAnotherFromOtherFile { return YetAnotherFromOtherFile(v: 1.2) }
65+
func overloadFromOtherFile() -> Bool { return false }
66+
if YetAnotherFromOtherFile(v: 1.9) == overloadFromOtherFile() {}
67+
68+
69+
// Even if the struct has only equatable/hashable members, it's not synthesized
70+
// implicitly.
71+
struct StructWithoutExplicitConformance {
72+
let a: Int
73+
let b: String
74+
}
75+
76+
if StructWithoutExplicitConformance(a: 1, b: "b") == StructWithoutExplicitConformance(a: 2, b: "a") { } // expected-error{{binary operator '==' cannot be applied to two 'StructWithoutExplicitConformance' operands}}
77+
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
78+
79+
80+
// Structs with non-hashable/equatable stored properties don't derive conformance.
81+
struct NotHashable {}
82+
struct StructWithNonHashablePayload: Hashable { // expected-error 2 {{does not conform}}
83+
let a: NotHashable
84+
}
85+
86+
// ...but computed properties and static properties are not considered.
87+
struct StructIgnoresComputedProperties: Hashable {
88+
var a: Int
89+
var b: String
90+
static var staticComputed = NotHashable()
91+
var computed: NotHashable { return NotHashable() }
92+
}
93+
if StructIgnoresComputedProperties(a: 1, b: "a") == StructIgnoresComputedProperties(a: 2, b: "c") {}
94+
let _: Int = StructIgnoresComputedProperties(a: 3, b: "p").hashValue
95+
96+
97+
// Structs should be able to derive conformances based on the conformances of
98+
// their generic arguments.
99+
struct GenericHashable<T: Hashable>: Hashable {
100+
let value: T
101+
}
102+
if GenericHashable<String>(value: "a") == GenericHashable<String>(value: "b") { }
103+
var genericHashableHash: Int = GenericHashable<String>(value: "a").hashValue
104+
105+
// But it should be an error if the generic argument doesn't have the necessary
106+
// constrants to satisfy the conditions for derivation.
107+
struct GenericNotHashable<T: Equatable>: Hashable { // expected-error 2 {{does not conform}}
108+
let value: T
109+
}
110+
if GenericNotHashable<String>(value: "a") == GenericNotHashable<String>(value: "b") { }
111+
var genericNotHashableHash: Int = GenericNotHashable<String>(value: "a").hashValue // expected-error {{value of type 'GenericNotHashable<String>' has no member 'hashValue'}}
112+
113+
114+
// Conformance can be synthesized in an extension.
115+
struct StructConformsInExtension {
116+
let v: Int
117+
}
118+
extension StructConformsInExtension : Equatable {}
119+
120+
// Explicit conformance in an extension should work too.
121+
public struct StructConformsAndImplementsInExtension {
122+
let v: Int
123+
}
124+
extension StructConformsAndImplementsInExtension : Equatable {
125+
public static func ==(lhs: StructConformsAndImplementsInExtension, rhs: StructConformsAndImplementsInExtension) -> Bool {
126+
return true
127+
}
128+
}
129+
130+
// No explicit conformance and it cannot be derived.
131+
struct NotExplicitlyHashableAndCannotDerive { // expected-error 2 {{does not conform}}
132+
let v: NotHashable
133+
}
134+
extension NotExplicitlyHashableAndCannotDerive : Hashable {}
135+
136+
// A struct with no stored properties trivially derives conformance.
137+
struct NoStoredProperties: Hashable {}
138+
139+
// FIXME: Remove -verify-ignore-unknown.
140+
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
141+
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'
142+
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '<T> (Generic<T>, Generic<T>) -> Bool'
143+
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(InvalidCustomHashable, InvalidCustomHashable) -> Bool'
144+
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(EnumToUseBeforeDeclaration, EnumToUseBeforeDeclaration) -> Bool'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %target-run-simple-swift
2+
3+
// REQUIRES: executable-test
4+
5+
struct Value: Hashable {
6+
let v: Int
7+
}
8+
9+
guard Value(v: 1) == Value(v: 1) else { fatalError() }
10+
guard Value(v: 1) != Value(v: 2) else { fatalError() }
11+
_ = Value(v: 1).hashValue
12+
_ = Value(v: 2).hashValue
13+
14+
// Try something a little more complex.
15+
struct Pair<T: Hashable, U: Hashable>: Hashable {
16+
let a: T
17+
let b: U
18+
}
19+
typealias PSI = Pair<String, Int>
20+
21+
guard PSI(a: "foo", b: 0) == PSI(a: "foo", b: 0) else { fatalError() }
22+
guard PSI(a: "foo", b: 0) != PSI(a: "foo", b: 5) else { fatalError() }
23+
guard PSI(a: "foo", b: 0) != PSI(a: "bar", b: 0) else { fatalError() }
24+
_ = PSI(a: "foo", b: 0).hashValue
25+
26+
// Make sure that if the user overrides the synthesized member, that one gets
27+
// used instead.
28+
struct Overrides: Hashable {
29+
let a: Int
30+
var hashValue: Int { return 2 }
31+
static func == (lhs: Overrides, rhs: Overrides) -> Bool { return true }
32+
}
33+
guard Overrides(a: 4) == Overrides(a: 5) else { fatalError() }
34+
guard Overrides(a: 4).hashValue == 2 else { fatalError() }
35+
guard Overrides(a: 5).hashValue == 2 else { fatalError() }
36+
37+
// ...even in an extension.
38+
struct OverridesInExtension: Hashable {
39+
let a: Int
40+
}
41+
extension OverridesInExtension {
42+
var hashValue: Int { return 2 }
43+
static func == (lhs: OverridesInExtension, rhs: OverridesInExtension) -> Bool { return true }
44+
}
45+
guard OverridesInExtension(a: 4) == OverridesInExtension(a: 5) else { fatalError() }
46+
guard OverridesInExtension(a: 4).hashValue == 2 else { fatalError() }
47+
guard OverridesInExtension(a: 5).hashValue == 2 else { fatalError() }

0 commit comments

Comments
 (0)