Skip to content

Commit b871528

Browse files
authored
Merge pull request #32524 from OnyekachiSamuel/fix-confusing-protocol-diagnostic
[Diagnostics] Fix Confusing Protocol Diagnostic
2 parents d87e7c8 + a08f421 commit b871528

19 files changed

+83
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,11 +1801,11 @@ ERROR(type_cannot_conform_to_nsobject,none,
18011801

18021802
ERROR(use_of_equal_instead_of_equality,none,
18031803
"use of '=' in a boolean context, did you mean '=='?", ())
1804-
18051804
ERROR(type_cannot_conform, none,
1806-
"%select{|value of protocol }0type %1 cannot conform to %2; "
1807-
"only struct/enum/class types can conform to protocols",
1808-
(bool, Type, Type))
1805+
"%select{type %1|protocol %1 as a type}0 cannot conform to "
1806+
"%select{%3|the protocol itself}2; "
1807+
"only concrete types such as structs, enums and classes can conform to protocols",
1808+
(bool, Type, bool, Type))
18091809
NOTE(required_by_opaque_return,none,
18101810
"required by opaque return type of %0 %1", (DescriptiveDeclKind, DeclName))
18111811
NOTE(required_by_decl,none,

include/swift/AST/EducationalNotes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ EDUCATIONAL_NOTES(append_interpolation_static,
6565
"string-interpolation-conformance.md")
6666
EDUCATIONAL_NOTES(append_interpolation_void_or_discardable,
6767
"string-interpolation-conformance.md")
68+
EDUCATIONAL_NOTES(type_cannot_conform, "protocol-type-non-conformance.md")
6869

6970
#undef EDUCATIONAL_NOTES

lib/Sema/CSDiagnostics.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,9 @@ bool MissingConformanceFailure::diagnoseTypeCannotConform(
500500
}
501501

502502
emitDiagnostic(diag::type_cannot_conform,
503-
nonConformingType->isExistentialType(), nonConformingType,
503+
nonConformingType->isExistentialType(),
504+
nonConformingType,
505+
nonConformingType->isEqual(protocolType),
504506
protocolType);
505507

506508
if (auto *OTD = dyn_cast<OpaqueTypeDecl>(AffectedDecl)) {
@@ -2087,7 +2089,8 @@ bool ContextualFailure::diagnoseAsError() {
20872089
if (CTP == CTP_ForEachStmt) {
20882090
if (fromType->isAnyExistentialType()) {
20892091
emitDiagnostic(diag::type_cannot_conform,
2090-
/*isExistentialType=*/true, fromType, toType);
2092+
/*isExistentialType=*/true, fromType,
2093+
fromType->isEqual(toType), toType);
20912094
return true;
20922095
}
20932096

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4312,7 +4312,8 @@ void swift::diagnoseConformanceFailure(Type T,
43124312

43134313
if (!T->isObjCExistentialType()) {
43144314
diags.diagnose(ComplainLoc, diag::type_cannot_conform, true,
4315-
T, Proto->getDeclaredType());
4315+
T, T->isEqual(Proto->getDeclaredType()),
4316+
Proto->getDeclaredType());
43164317
return;
43174318
}
43184319

test/Constraints/diagnostics.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ f0(i, i, // expected-error@:7 {{cannot convert value of type 'Int' to expected a
5151

5252

5353
// Cannot conform to protocols.
54-
f5(f4) // expected-error {{type '(Int) -> Int' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
55-
f5((1, "hello")) // expected-error {{type '(Int, String)' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
56-
f5(Int.self) // expected-error {{type 'Int.Type' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
54+
f5(f4) // expected-error {{type '(Int) -> Int' cannot conform to 'P2'; only concrete types such as structs, enums and classes can conform to protocols}}
55+
f5((1, "hello")) // expected-error {{type '(Int, String)' cannot conform to 'P2'; only concrete types such as structs, enums and classes can conform to protocols}}
56+
f5(Int.self) // expected-error {{type 'Int.Type' cannot conform to 'P2'; only concrete types such as structs, enums and classes can conform to protocols}}
5757

5858
// Tuple element not convertible.
5959
f0(i,
@@ -104,7 +104,7 @@ func f8<T:P2>(_ n: T, _ f: @escaping (T) -> T) {} // expected-note {{where 'T'
104104
f8(3, f4) // expected-error {{global function 'f8' requires that 'Int' conform to 'P2'}}
105105
typealias Tup = (Int, Double)
106106
func f9(_ x: Tup) -> Tup { return x }
107-
f8((1,2.0), f9) // expected-error {{type 'Tup' (aka '(Int, Double)') cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
107+
f8((1,2.0), f9) // expected-error {{type 'Tup' (aka '(Int, Double)') cannot conform to 'P2'; only concrete types such as structs, enums and classes can conform to protocols}}
108108

109109
// <rdar://problem/19658691> QoI: Incorrect diagnostic for calling nonexistent members on literals
110110
1.doesntExist(0) // expected-error {{value of type 'Int' has no member 'doesntExist'}}

test/Constraints/function_builder_diags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ struct Label<L> : P where L : P { // expected-note 2 {{'L' declared as parameter
179179
}
180180

181181
func test_51167632() -> some P {
182-
AnyP(G { // expected-error {{type 'Label<_>.Type' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
182+
AnyP(G { // expected-error {{type 'Label<_>.Type' cannot conform to 'P'; only concrete types such as structs, enums and classes can conform to protocols}}
183183
Text("hello")
184184
Label // expected-error {{generic parameter 'L' could not be inferred}}
185185
// expected-note@-1 {{explicitly specify the generic arguments to fix this issue}} {{10-10=<<#L: P#>>}}

test/Constraints/generics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func r22459135() {
188188

189189
// <rdar://problem/19710848> QoI: Friendlier error message for "[] as Set"
190190
// <rdar://problem/22326930> QoI: "argument for generic parameter 'Element' could not be inferred" lacks context
191-
_ = [] as Set // expected-error {{value of protocol type 'Any' cannot conform to 'Hashable'; only struct/enum/class types can conform to protocols}}
191+
_ = [] as Set // expected-error {{protocol 'Any' as a type cannot conform to 'Hashable'; only concrete types such as structs, enums and classes can conform to protocols}}
192192
// expected-note@-1 {{required by generic struct 'Set' where 'Element' = 'Any'}}
193193

194194

test/Generics/conditional_conformances_literals.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ func combined() {
128128

129129
// Needs self conforming protocols:
130130
let _: Conforms = [[0: [1 : [works]] as Conforms]]
131-
// expected-error@-1 {{value of protocol type 'Conforms' cannot conform to 'Conforms'; only struct/enum/class types can conform to protocols}}
131+
// expected-error@-1 {{protocol 'Conforms' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
132132

133133
let _: Conforms = [[0: [1 : [fails]] as Conforms]]
134134
// expected-error@-1 {{protocol 'Conforms' requires that 'Fails' conform to 'Conforms'}}
135-
// expected-error@-2 {{value of protocol type 'Conforms' cannot conform to 'Conforms'; only struct/enum/class types can conform to protocols}}
135+
// expected-error@-2 {{protocol 'Conforms' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
136136
}

test/Generics/existential_restrictions.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func fAOE(_ t: AnyObject) { }
2323
func fT<T>(_ t: T) { }
2424

2525
func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any, ao: AnyObject) {
26-
fP(p) // expected-error{{value of protocol type 'P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
26+
fP(p) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
2727
fAO(p) // expected-error{{global function 'fAO' requires that 'P' be a class type}}
2828
fAOE(p) // expected-error{{argument type 'P' expected to be an instance of a class or class-constrained type}}
2929
fT(p)
@@ -37,8 +37,8 @@ func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any,
3737
fAOE(cp)
3838
fT(cp)
3939

40-
fP(opp) // expected-error{{value of protocol type 'OP & P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
41-
fOP(opp) // expected-error{{value of protocol type 'OP & P' cannot conform to 'OP'; only struct/enum/class types can conform to protocols}}
40+
fP(opp) // expected-error{{protocol 'OP & P' as a type cannot conform to 'P'; only concrete types such as structs, enums and classes can conform to protocols}}
41+
fOP(opp) // expected-error{{protocol 'OP & P' as a type cannot conform to 'OP'; only concrete types such as structs, enums and classes can conform to protocols}}
4242
fAO(opp) // expected-error{{global function 'fAO' requires that 'OP & P' be a class type}}
4343
fAOE(opp)
4444
fT(opp)
@@ -64,9 +64,9 @@ class GAO<T : AnyObject> {} // expected-note 2{{requirement specified as 'T' : '
6464
func blackHole(_ t: Any) {}
6565

6666
func testBindExistential() {
67-
blackHole(GP<P>()) // expected-error{{value of protocol type 'P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
67+
blackHole(GP<P>()) // expected-error{{protocol 'P' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
6868
blackHole(GOP<OP>())
69-
blackHole(GCP<CP>()) // expected-error{{value of protocol type 'CP' cannot conform to 'CP'; only struct/enum/class types can conform to protocols}}
69+
blackHole(GCP<CP>()) // expected-error{{protocol 'CP' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
7070
blackHole(GAO<P>()) // expected-error{{'GAO' requires that 'P' be a class type}}
7171
blackHole(GAO<OP>())
7272
blackHole(GAO<CP>()) // expected-error{{'GAO' requires that 'CP' be a class type}}
@@ -92,5 +92,5 @@ func foo() {
9292
// generic no overloads error path. The error should actually talk
9393
// about the return type, and this can happen in other contexts as well;
9494
// <rdar://problem/21900971> tracks improving QoI here.
95-
allMine.takeAll() // expected-error{{value of protocol type 'Mine' cannot conform to 'Mine'; only struct/enum/class types can conform to protocols}}
95+
allMine.takeAll() // expected-error{{protocol 'Mine' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
9696
}

test/Misc/misc_diagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func test17875634() {
143143
func test20770032() {
144144
if case let 1...10 = (1, 1) { // expected-warning{{'let' pattern has no effect; sub-pattern didn't bind any variables}} {{11-15=}}
145145
// expected-error@-1 {{expression pattern of type 'ClosedRange<Int>' cannot match values of type '(Int, Int)'}}
146-
// expected-error@-2 {{'(Int, Int)' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols}}
146+
// expected-error@-2 {{type '(Int, Int)' cannot conform to 'Equatable'; only concrete types such as structs, enums and classes can conform to protocols}}
147147
// expected-note@-3 {{required by operator function '~=' where 'T' = '(Int, Int)'}}
148148
}
149149
}

test/Parse/confusables.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ if (true ꝸꝸꝸ false) {} // expected-note {{identifier 'ꝸꝸꝸ' contains
1717

1818
// expected-error @+3 {{invalid character in source file}}
1919
// expected-error @+2 {{expected ',' separator}}
20-
// expected-error @+1 {{type '(Int, Int)' cannot conform to 'BinaryInteger'; only struct/enum/class types can conform to protocols}}
20+
// expected-error @+1 {{type '(Int, Int)' cannot conform to 'BinaryInteger'; only concrete types such as structs, enums and classes can conform to protocols}}
2121
if (55) == 0 {} // expected-note {{unicode character '‒' looks similar to '-'; did you mean to use '-'?}} {{7-10=-}}
2222
// expected-note @-1 {{operator function '=='}}
2323

test/decl/protocol/conforms/error_self_conformance.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ func testSimple(error: Error) {
1111

1212
protocol ErrorRefinement : Error {}
1313
func testErrorRefinment(error: ErrorRefinement) {
14-
wantsError(error) // expected-error {{value of protocol type 'ErrorRefinement' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
14+
wantsError(error) // expected-error {{protocol 'ErrorRefinement' as a type cannot conform to 'Error'; only concrete types such as structs, enums and classes can conform to protocols}}
1515
}
1616

1717
protocol OtherProtocol {}
1818
func testErrorComposition(error: Error & OtherProtocol) {
19-
wantsError(error) // expected-error {{value of protocol type 'Error & OtherProtocol' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
19+
wantsError(error) // expected-error {{protocol 'Error & OtherProtocol' as a type cannot conform to 'Error'; only concrete types such as structs, enums and classes can conform to protocols}}
2020
}
2121

2222
class C {}
2323
func testErrorCompositionWithClass(error: Error & C) {
24-
wantsError(error) // expected-error {{value of protocol type 'C & Error' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
24+
wantsError(error) // expected-error {{protocol 'C & Error' as a type cannot conform to 'Error'; only concrete types such as structs, enums and classes can conform to protocols}}
2525
}

test/stmt/foreach.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func testOptionalSequence() {
177177

178178
// Crash with (invalid) for each over an existential
179179
func testExistentialSequence(s: Sequence) { // expected-error {{protocol 'Sequence' can only be used as a generic constraint because it has Self or associated type requirements}}
180-
for x in s { // expected-error {{value of protocol type 'Sequence' cannot conform to 'Sequence'; only struct/enum/class types can conform to protocols}}
180+
for x in s { // expected-error {{protocol 'Sequence' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
181181
_ = x
182182
}
183183
}

test/type/opaque.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ protocol P_51641323 {
383383
func rdar_51641323() {
384384
struct Foo: P_51641323 {
385385
var foo: some P_51641323 { // expected-note {{required by opaque return type of property 'foo'}}
386-
{} // expected-error {{type '() -> ()' cannot conform to 'P_51641323'; only struct/enum/class types can conform to protocols}}
386+
{} // expected-error {{type '() -> ()' cannot conform to 'P_51641323'; only concrete types such as structs, enums and classes can conform to protocols}}
387387
}
388388
}
389389
}

test/type/subclass_composition.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ func conformsTo<T1 : P2, T2 : Base<Int> & P2>(
413413
// expected-error@-1 {{global function 'conformsToAnyObject' requires that 'P1' be a class type}}
414414

415415
conformsToP1(p1)
416-
// expected-error@-1 {{value of protocol type 'P1' cannot conform to 'P1'; only struct/enum/class types can conform to protocols}}
416+
// expected-error@-1 {{protocol 'P1' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}
417417

418418
// FIXME: Following diagnostics are not great because when
419419
// `conformsTo*` methods are re-typechecked, they loose information
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Protocol type not conforming to itself
2+
Protocols in Swift may be used as types. Protocols as types are sometimes called existential types.
3+
4+
5+
```swift
6+
protocol P {}
7+
8+
struct S: P {}
9+
10+
var s: P = S() // This creates existential type because the protocol P is used as a type
11+
```
12+
13+
However, a protocol type does not conform to protocols - not even the protocol itself.
14+
Allowing existential types to conform to protocols is unsound. For protocols with static method, initializer, or associated type requirements, the implementation of these requirements cannot be accessed from the protocol type - accessing these kinds of requirements must be done using a concrete type.
15+
16+
Let's walk through the example below:
17+
18+
```swift
19+
protocol Word: Hashable {
20+
var word: String { get }
21+
}
22+
23+
struct Singular: Word {
24+
var word: String
25+
}
26+
27+
struct Plural: Word {
28+
var word: String
29+
}
30+
31+
let singularWord = Singular(word: "mango")
32+
let pluralWord = Plural(word: "mangoes")
33+
34+
let wordPairDict: [Word: Word] = [singularWord: pluralWord] // Error
35+
```
36+
37+
One workaround to fix this problem is to use type erasure for the protocol `Word`. Think of type erasure as a way to hide an object's type. Since `Word` is of type `Hashable`, we already have `AnyHashable` type erasure available in the standard library which we can easily use here.
38+
39+
```swift
40+
// The fix
41+
let wordPairDict: [AnyHashable: AnyHashable] = [singularWord: pluralWord]
42+
```
43+
44+
# Exceptions
45+
`@objc` protocol type with no static requirements however do conform to its own protocol. Another exception is the `Error` Swift protocol.
46+

validation-test/Sema/type_checker_crashers_fixed/rdar27830834.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
var d = [String:String]()
44
_ = "\(d.map{ [$0 : $0] })"
5-
// expected-error@-1 {{type 'Dictionary<String, String>.Element' (aka '(key: String, value: String)') cannot conform to 'Hashable'; only struct/enum/class types can conform to protocols}}
5+
// expected-error@-1 {{type 'Dictionary<String, String>.Element' (aka '(key: String, value: String)') cannot conform to 'Hashable'; only concrete types such as structs, enums and classes can conform to protocols}}
66
// expected-note@-2 {{required by generic struct 'Dictionary' where 'Key' = 'Dictionary<String, String>.Element' (aka '(key: String, value: String)')}}

validation-test/compiler_crashers_2_fixed/0196-rdar48937223.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ func fn<T, U: P>(_ arg1: T, arg2: (T) -> U) {}
66
// expected-note@-1 {{required by global function 'fn(_:arg2:)' where 'U' = '()'}}
77

88
func test(str: String) {
9-
fn(str) { arg in // expected-error {{type '()' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
9+
fn(str) { arg in // expected-error {{type '()' cannot conform to 'P'; only concrete types such as structs, enums and classes can conform to protocols}}
1010
<#FOO#> // expected-error {{editor placeholder in source file}}
1111
}
1212
}

validation-test/compiler_crashers_fixed/00017-llvm-foldingset-llvm-attributesetnode-nodeequals.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ extension Bool : BooleanProtocol {
2020
func f<T : BooleanProtocol>(_ b: T) {}
2121
// expected-note@-1 {{required by global function 'f' where 'T' = 'BooleanProtocol'}}
2222

23-
f(true as BooleanProtocol) // expected-error {{value of protocol type 'BooleanProtocol' cannot conform to 'BooleanProtocol'; only struct/enum/class types can conform to protocols}}
23+
f(true as BooleanProtocol) // expected-error {{protocol 'BooleanProtocol' as a type cannot conform to the protocol itself; only concrete types such as structs, enums and classes can conform to protocols}}

0 commit comments

Comments
 (0)