Skip to content

Commit 873416f

Browse files
authored
Merge pull request #17832 from huonw/crossfile-synthesis-implication-4.2
[AST] Choose an implied conformance source next to the type, if possible.
2 parents d9561d9 + e52d7a0 commit 873416f

File tree

5 files changed

+184
-98
lines changed

5 files changed

+184
-98
lines changed

lib/AST/ConformanceLookupTable.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,19 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
672672
: Ordering::After;
673673
}
674674

675+
// If one of the conformances comes from the same file as the type
676+
// declaration, pick that one; this is so that conformance synthesis works if
677+
// there's any implied conformance in the same file as the type.
678+
auto NTD =
679+
lhs->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext();
680+
auto typeSF = NTD->getParentSourceFile();
681+
if (typeSF) {
682+
if (typeSF == lhsSF)
683+
return Ordering::Before;
684+
if (typeSF == rhsSF)
685+
return Ordering::After;
686+
}
687+
675688
// Otherwise, pick the earlier file unit.
676689
auto lhsFileUnit
677690
= dyn_cast<FileUnit>(lhs->getDeclContext()->getModuleScopeContext());

test/Sema/Inputs/enum_conformance_synthesis_other.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ enum GenericOtherFileNonconforming<T> {
2323
// expected-note@-1 {{type declared here}}
2424
case A(T)
2525
}
26+
27+
protocol ImplierOther: Equatable {}
28+
extension ImpliedMain: ImplierMain {}
29+
enum ImpliedOther: ImplierOther {
30+
case a(Int)
31+
}

test/Sema/Inputs/struct_equatable_hashable_other.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ struct GenericOtherFileNonconforming<T> {
2222
// expected-note@-1{{type declared here}}
2323
let v: T
2424
}
25+
26+
protocol ImplierOther: Equatable {}
27+
extension ImpliedMain: ImplierOther {}
28+
struct ImpliedOther: ImplierOther {}

test/Sema/enum_conformance_synthesis.swift

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
// RUN: %empty-directory(%t)
2-
// RUN: cp %s %t/main.swift
3-
// RUN: %target-swift-frontend -typecheck -verify -primary-file %t/main.swift %S/Inputs/enum_conformance_synthesis_other.swift -verify-ignore-unknown -swift-version 4
1+
// RUN: %target-swift-frontend -typecheck -verify -primary-file %s %S/Inputs/enum_conformance_synthesis_other.swift -verify-ignore-unknown -swift-version 4
42

53
var hasher = Hasher()
64

75
enum Foo: CaseIterable {
86
case A, B
97
}
108

11-
if Foo.A == .B { }
12-
var aHash: Int = Foo.A.hashValue
13-
Foo.A.hash(into: &hasher)
14-
_ = Foo.allCases
9+
func foo() {
10+
if Foo.A == .B { }
11+
var _: Int = Foo.A.hashValue
12+
Foo.A.hash(into: &hasher)
13+
_ = Foo.allCases
1514

16-
Foo.A == Foo.B // expected-warning {{result of operator '==' is unused}}
15+
Foo.A == Foo.B // expected-warning {{result of operator '==' is unused}}
16+
}
1717

1818
enum Generic<T>: CaseIterable {
1919
case A, B
@@ -25,10 +25,12 @@ enum Generic<T>: CaseIterable {
2525
}
2626
}
2727

28-
if Generic<Foo>.A == .B { }
29-
var gaHash: Int = Generic<Foo>.A.hashValue
30-
Generic<Foo>.A.hash(into: &hasher)
31-
_ = Generic<Foo>.allCases
28+
func generic() {
29+
if Generic<Foo>.A == .B { }
30+
var _: Int = Generic<Foo>.A.hashValue
31+
Generic<Foo>.A.hash(into: &hasher)
32+
_ = Generic<Foo>.allCases
33+
}
3234

3335
func localEnum() -> Bool {
3436
enum Local {
@@ -47,9 +49,11 @@ func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 5 {{non
4749
return true
4850
}
4951

50-
if CustomHashable.A == .B { }
51-
var custHash: Int = CustomHashable.A.hashValue
52-
CustomHashable.A.hash(into: &hasher)
52+
func customHashable() {
53+
if CustomHashable.A == .B { }
54+
var _: Int = CustomHashable.A.hashValue
55+
CustomHashable.A.hash(into: &hasher)
56+
}
5357

5458
// We still synthesize conforming overloads of '==' and 'hashValue' if
5559
// explicit definitions don't satisfy the protocol requirements. Probably
@@ -62,11 +66,14 @@ enum InvalidCustomHashable {
6266
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 5 {{non-matching type}}
6367
return ""
6468
}
65-
if InvalidCustomHashable.A == .B { }
66-
var s: String = InvalidCustomHashable.A == .B
67-
s = InvalidCustomHashable.A.hashValue
68-
var i: Int = InvalidCustomHashable.A.hashValue
69-
InvalidCustomHashable.A.hash(into: &hasher)
69+
func invalidCustomHashable() {
70+
if InvalidCustomHashable.A == .B { }
71+
var s: String = InvalidCustomHashable.A == .B
72+
s = InvalidCustomHashable.A.hashValue
73+
_ = s
74+
var _: Int = InvalidCustomHashable.A.hashValue
75+
InvalidCustomHashable.A.hash(into: &hasher)
76+
}
7077

7178
// Check use of an enum's synthesized members before the enum is actually declared.
7279
struct UseEnumBeforeDeclaration {
@@ -77,25 +84,30 @@ enum EnumToUseBeforeDeclaration {
7784
case A
7885
}
7986

80-
// Check enums from another file in the same module.
81-
if FromOtherFile.A == .A {}
82-
let _: Int = FromOtherFile.A.hashValue
83-
8487
func getFromOtherFile() -> AlsoFromOtherFile { return .A }
85-
if .A == getFromOtherFile() {}
86-
8788
func overloadFromOtherFile() -> YetAnotherFromOtherFile { return .A }
8889
func overloadFromOtherFile() -> Bool { return false }
89-
if .A == overloadFromOtherFile() {}
90+
91+
func useEnumBeforeDeclaration() {
92+
// Check enums from another file in the same module.
93+
if FromOtherFile.A == .A {}
94+
let _: Int = FromOtherFile.A.hashValue
95+
96+
if .A == getFromOtherFile() {}
97+
98+
if .A == overloadFromOtherFile() {}
99+
}
90100

91101
// Complex enums are not automatically Equatable, Hashable, or CaseIterable.
92102
enum Complex {
93103
case A(Int)
94104
case B
95105
}
96106

97-
if Complex.A(1) == .B { } // expected-error{{binary operator '==' cannot be applied to operands of type 'Complex' and '_'}}
98-
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
107+
func complex() {
108+
if Complex.A(1) == .B { } // expected-error{{binary operator '==' cannot be applied to operands of type 'Complex' and '_'}}
109+
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
110+
}
99111

100112
// Enums with equatable payloads are equatable if they explicitly conform.
101113
enum EnumWithEquatablePayload: Equatable {
@@ -104,9 +116,11 @@ enum EnumWithEquatablePayload: Equatable {
104116
case C
105117
}
106118

107-
if EnumWithEquatablePayload.A(1) == .B("x", 1) { }
108-
if EnumWithEquatablePayload.A(1) == .C { }
109-
if EnumWithEquatablePayload.B("x", 1) == .C { }
119+
func enumWithEquatablePayload() {
120+
if EnumWithEquatablePayload.A(1) == .B("x", 1) { }
121+
if EnumWithEquatablePayload.A(1) == .C { }
122+
if EnumWithEquatablePayload.B("x", 1) == .C { }
123+
}
110124

111125
// Enums with hashable payloads are hashable if they explicitly conform.
112126
enum EnumWithHashablePayload: Hashable {
@@ -115,18 +129,20 @@ enum EnumWithHashablePayload: Hashable {
115129
case C
116130
}
117131

118-
_ = EnumWithHashablePayload.A(1).hashValue
119-
_ = EnumWithHashablePayload.B("x", 1).hashValue
120-
_ = EnumWithHashablePayload.C.hashValue
132+
func enumWithHashablePayload() {
133+
_ = EnumWithHashablePayload.A(1).hashValue
134+
_ = EnumWithHashablePayload.B("x", 1).hashValue
135+
_ = EnumWithHashablePayload.C.hashValue
121136

122-
EnumWithHashablePayload.A(1).hash(into: &hasher)
123-
EnumWithHashablePayload.B("x", 1).hash(into: &hasher)
124-
EnumWithHashablePayload.C.hash(into: &hasher)
137+
EnumWithHashablePayload.A(1).hash(into: &hasher)
138+
EnumWithHashablePayload.B("x", 1).hash(into: &hasher)
139+
EnumWithHashablePayload.C.hash(into: &hasher)
125140

126-
// ...and they should also inherit equatability from Hashable.
127-
if EnumWithHashablePayload.A(1) == .B("x", 1) { }
128-
if EnumWithHashablePayload.A(1) == .C { }
129-
if EnumWithHashablePayload.B("x", 1) == .C { }
141+
// ...and they should also inherit equatability from Hashable.
142+
if EnumWithHashablePayload.A(1) == .B("x", 1) { }
143+
if EnumWithHashablePayload.A(1) == .C { }
144+
if EnumWithHashablePayload.B("x", 1) == .C { }
145+
}
130146

131147
// Enums with non-hashable payloads don't derive conformance.
132148
struct NotHashable {}
@@ -140,18 +156,22 @@ enum GenericHashable<T: Hashable>: Hashable {
140156
case A(T)
141157
case B
142158
}
143-
if GenericHashable<String>.A("a") == .B { }
144-
var genericHashableHash: Int = GenericHashable<String>.A("a").hashValue
159+
func genericHashable() {
160+
if GenericHashable<String>.A("a") == .B { }
161+
var _: Int = GenericHashable<String>.A("a").hashValue
162+
}
145163

146164
// But it should be an error if the generic argument doesn't have the necessary
147165
// constraints to satisfy the conditions for derivation.
148166
enum GenericNotHashable<T: Equatable>: Hashable { // expected-error 2 {{does not conform to protocol 'Hashable'}}
149167
case A(T)
150168
case B
151169
}
152-
if GenericNotHashable<String>.A("a") == .B { }
153-
let _: Int = GenericNotHashable<String>.A("a").hashValue // No error. hashValue is always synthesized, even if Hashable derivation fails
154-
GenericNotHashable<String>.A("a").hash(into: &hasher) // expected-error {{value of type 'GenericNotHashable<String>' has no member 'hash'}}
170+
func genericNotHashable() {
171+
if GenericNotHashable<String>.A("a") == .B { }
172+
let _: Int = GenericNotHashable<String>.A("a").hashValue // No error. hashValue is always synthesized, even if Hashable derivation fails
173+
GenericNotHashable<String>.A("a").hash(into: &hasher) // expected-error {{value of type 'GenericNotHashable<String>' has no member 'hash'}}
174+
}
155175

156176
// An enum with no cases should not derive conformance.
157177
enum NoCases: Hashable {} // expected-error 2 {{does not conform}}
@@ -193,13 +213,17 @@ public enum Medicine {
193213

194214
extension Medicine : Equatable {}
195215

196-
public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 4 {{non-matching type}}
216+
public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 5 {{non-matching type}}
197217
return true
198218
}
199219

200220
// No explicit conformance; but it can be derived, for the same-file cases.
201-
extension Complex : Hashable {}
202-
extension Complex : CaseIterable {} // expected-error {{type 'Complex' does not conform to protocol 'CaseIterable'}}
221+
enum Complex2 {
222+
case A(Int)
223+
case B
224+
}
225+
extension Complex2 : Hashable {}
226+
extension Complex2 : CaseIterable {} // expected-error {{type 'Complex2' does not conform to protocol 'CaseIterable'}}
203227
extension FromOtherFile: CaseIterable {} // expected-error {{cannot be automatically synthesized in an extension in a different file to the type}} expected-error {{does not conform to protocol 'CaseIterable'}}
204228

205229
// No explicit conformance and it cannot be derived.
@@ -212,7 +236,7 @@ extension NotExplicitlyHashableAndCannotDerive : CaseIterable {} // expected-err
212236
// Verify that conformance (albeit manually implemented) can still be added to
213237
// a type in a different file.
214238
extension OtherFileNonconforming: Hashable {
215-
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 4 {{non-matching type}}
239+
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 5 {{non-matching type}}
216240
return true
217241
}
218242
var hashValue: Int { return 0 }
@@ -290,6 +314,18 @@ extension UnusedGenericDeriveExtension: Hashable {}
290314
extension GenericOtherFileNonconforming: Equatable where T: Equatable {}
291315
// expected-error@-1{{implementation of 'Equatable' cannot be automatically synthesized in an extension in a different file to the type}}
292316

317+
// rdar://problem/41852654
318+
319+
// There is a conformance to Equatable (or at least, one that implies Equatable)
320+
// in the same file as the type, so the synthesis is okay. Both orderings are
321+
// tested, to catch choosing extensions based on the order of the files, etc.
322+
protocol ImplierMain: Equatable {}
323+
enum ImpliedMain: ImplierMain {
324+
case a(Int)
325+
}
326+
extension ImpliedOther: ImplierMain {}
327+
328+
293329
// FIXME: Remove -verify-ignore-unknown.
294330
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
295331
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'

0 commit comments

Comments
 (0)