Skip to content

Commit 0aabee5

Browse files
authored
[Typechecker] Emit specialized diagnostic notes on automatic synthesis failure to Comparable (#32797)
1 parent 09bc359 commit 0aabee5

File tree

9 files changed

+125
-49
lines changed

9 files changed

+125
-49
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,9 +2833,12 @@ NOTE(missing_member_type_conformance_prevents_synthesis, none,
28332833
"protocol %2, preventing synthesized conformance "
28342834
"of %3 to %2",
28352835
(unsigned, Type, Type, Type))
2836-
NOTE(classes_automatic_protocol_synthesis,none,
2837-
"automatic synthesis of '%0' is not supported for classes",
2838-
(StringRef))
2836+
NOTE(automatic_protocol_synthesis_unsupported,none,
2837+
"automatic synthesis of '%0' is not supported for %select{classes|structs}1",
2838+
(StringRef, unsigned))
2839+
NOTE(comparable_synthesis_raw_value_not_allowed, none,
2840+
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
2841+
(Type, Type, Type))
28392842

28402843
// Dynamic Self
28412844
ERROR(dynamic_self_non_method,none,

lib/Sema/DerivedConformanceComparable.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,40 @@ ValueDecl *DerivedConformance::deriveComparable(ValueDecl *requirement) {
338338
}
339339
return deriveComparable_lt(*this, synthesizer);
340340
}
341+
342+
void DerivedConformance::tryDiagnoseFailedComparableDerivation(
343+
DeclContext *DC, NominalTypeDecl *nominal) {
344+
auto &ctx = DC->getASTContext();
345+
auto *comparableProto = ctx.getProtocol(KnownProtocolKind::Comparable);
346+
if (!isa<EnumDecl>(nominal)) {
347+
auto contextDecl = DC->getAsDecl();
348+
ctx.Diags.diagnose(
349+
contextDecl->getLoc(), diag::automatic_protocol_synthesis_unsupported,
350+
comparableProto->getName().str(), isa<StructDecl>(contextDecl));
351+
}
352+
353+
if (auto *enumDecl = dyn_cast<EnumDecl>(nominal)) {
354+
auto nonconformingAssociatedTypes =
355+
DerivedConformance::associatedValuesNotConformingToProtocol(
356+
DC, enumDecl, comparableProto);
357+
for (auto *typeToDiagnose : nonconformingAssociatedTypes) {
358+
SourceLoc reprLoc;
359+
if (auto *repr = typeToDiagnose->getTypeRepr())
360+
reprLoc = repr->getStartLoc();
361+
ctx.Diags.diagnose(
362+
reprLoc, diag::missing_member_type_conformance_prevents_synthesis, 0,
363+
typeToDiagnose->getInterfaceType(),
364+
comparableProto->getDeclaredType(),
365+
nominal->getDeclaredInterfaceType());
366+
}
367+
368+
if (enumDecl->hasRawType() && !enumDecl->getRawType()->is<ErrorType>()) {
369+
auto rawType = enumDecl->getRawType();
370+
auto rawTypeLoc = enumDecl->getInherited()[0].getSourceRange().Start;
371+
ctx.Diags.diagnose(rawTypeLoc,
372+
diag::comparable_synthesis_raw_value_not_allowed,
373+
rawType, nominal->getDeclaredInterfaceType(),
374+
comparableProto->getDeclaredType());
375+
}
376+
}
377+
}

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ void diagnoseFailedDerivation(DeclContext *DC, NominalTypeDecl *nominal,
130130

131131
if (auto *classDecl = dyn_cast<ClassDecl>(nominal)) {
132132
ctx.Diags.diagnose(classDecl->getLoc(),
133-
diag::classes_automatic_protocol_synthesis,
134-
protocol->getName().str());
133+
diag::automatic_protocol_synthesis_unsupported,
134+
protocol->getName().str(), 0);
135135
}
136136
}
137137

lib/Sema/DerivedConformances.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ void DerivedConformance::tryDiagnoseFailedDerivation(DeclContext *DC,
175175
if (*knownProtocol == KnownProtocolKind::Hashable) {
176176
tryDiagnoseFailedHashableDerivation(DC, nominal);
177177
}
178+
179+
if (*knownProtocol == KnownProtocolKind::Comparable) {
180+
tryDiagnoseFailedComparableDerivation(DC, nominal);
181+
}
178182
}
179183

180184
ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,

lib/Sema/DerivedConformances.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ class DerivedConformance {
169169
/// \returns the derived member, which will also be added to the type.
170170
ValueDecl *deriveComparable(ValueDecl *requirement);
171171

172+
/// Diagnose problems, if any, preventing automatic derivation of Comparable
173+
/// requirements
174+
///
175+
/// \param nominal The nominal type for which we would like to diagnose
176+
/// derivation failures
177+
static void tryDiagnoseFailedComparableDerivation(DeclContext *DC,
178+
NominalTypeDecl *nominal);
179+
172180
/// Determine if an Equatable requirement can be derived for a type.
173181
///
174182
/// This is implemented for enums without associated values or all-Equatable

localization/diagnostics/en.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6701,9 +6701,13 @@
67016701
%select{associated value|stored property}0 type %1 does not conform to
67026702
protocol %2, preventing synthesized conformance of %3 to %2
67036703
6704-
- id: classes_automatic_protocol_synthesis
6704+
- id: automatic_protocol_synthesis_unsupported
67056705
msg: >-
6706-
automatic synthesis of '%0' is not supported for classes
6706+
automatic synthesis of '%0' is not supported for %select{classes|structs}1
6707+
6708+
- id: comparable_synthesis_raw_value_not_allowed
6709+
msg: >-
6710+
enum declares raw type %0, preventing synthesized conformance of %1 to %2
67076711
67086712
- id: dynamic_self_non_method
67096713
msg: >-

test/Sema/enum_conformance_synthesis.swift

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func genericNotHashable() {
174174
}
175175

176176
// An enum with no cases should also derive conformance.
177-
enum NoCases: Hashable, Comparable {}
177+
enum NoCases: Hashable {}
178178

179179
// rdar://19773050
180180
private enum Bar<T> {
@@ -327,47 +327,6 @@ enum ImpliedMain: ImplierMain {
327327
}
328328
extension ImpliedOther: ImplierMain {}
329329

330-
// Comparable enum synthesis
331-
enum Angel: Comparable {
332-
case lily, elsa, karlie
333-
}
334-
335-
func pit(_ a: Angel, against b: Angel) -> Bool {
336-
return a < b
337-
}
338-
339-
// enums with non-conforming payloads don’t get synthesized Comparable
340-
enum Notice: Comparable { // expected-error{{type 'Notice' does not conform to protocol 'Comparable'}} expected-error{{type 'Notice' does not conform to protocol 'Equatable'}}
341-
case taylor((Int, Int)), taylornation(Int) // expected-note{{associated value type '(Int, Int)' does not conform to protocol 'Equatable', preventing synthesized conformance of 'Notice' to 'Equatable'}}
342-
}
343-
344-
// neither do enums with raw values
345-
enum Track: Int, Comparable { // expected-error{{type 'Track' does not conform to protocol 'Comparable'}}
346-
case four = 4
347-
case five = 5
348-
case six = 6
349-
}
350-
351-
// synthesized Comparable must be explicit
352-
enum Publicist {
353-
case thow, paine
354-
}
355-
356-
func miss(_ a: Publicist, outsold b: Publicist) -> Bool {
357-
return b < a // expected-error{{binary operator '<' cannot be applied to two 'Publicist' operands}}
358-
}
359-
360-
// can synthesize Comparable conformance through extension
361-
enum Birthyear {
362-
case eighties(Int)
363-
case nineties(Int)
364-
case twothousands(Int)
365-
}
366-
extension Birthyear: Comparable {
367-
}
368-
func canEatHotChip(_ birthyear:Birthyear) -> Bool {
369-
return birthyear > .nineties(3)
370-
}
371330
// FIXME: Remove -verify-ignore-unknown.
372331
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'
373332
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '<T> (Generic<T>, Generic<T>) -> Bool'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Comparable enum synthesis
4+
enum Angel: Comparable {
5+
case lily, elsa, karlie
6+
}
7+
8+
func pit(_ a: Angel, against b: Angel) -> Bool {
9+
return a < b // Okay
10+
}
11+
12+
// An enum with no cases should also derive conformance to Comparable.
13+
14+
enum NoCasesEnum: Comparable {} // Okay
15+
16+
// Comparable enum conformance through extension
17+
enum Birthyear {
18+
case eighties(Int)
19+
case nineties(Int)
20+
case twothousands(Int)
21+
}
22+
23+
extension Birthyear: Comparable {}
24+
25+
func canEatHotChip(_ birthyear:Birthyear) -> Bool {
26+
return birthyear > .nineties(3) // Okay
27+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
// Automatic synthesis of Comparable is only supported for enums for now.
4+
5+
struct NotComparableStruct: Comparable {
6+
// expected-error@-1 {{type 'NotComparableStruct' does not conform to protocol 'Comparable'}}
7+
// expected-note@-2 {{automatic synthesis of 'Comparable' is not supported for structs}}
8+
var value = 0
9+
}
10+
11+
class NotComparableClass: Comparable {
12+
// expected-error@-1 {{type 'NotComparableClass' does not conform to protocol 'Comparable'}}
13+
// expected-note@-2 {{automatic synthesis of 'Comparable' is not supported for classes}}
14+
// expected-error@-3 {{type 'NotComparableClass' does not conform to protocol 'Equatable'}}
15+
// expected-note@-4 {{automatic synthesis of 'Equatable' is not supported for classes}}
16+
var value = 1
17+
}
18+
19+
// Automatic synthesis of Comparable requires enum without raw type.
20+
21+
enum NotComparableEnumOne: Int, Comparable {
22+
// expected-error@-1 {{type 'NotComparableEnumOne' does not conform to protocol 'Comparable'}}
23+
// expected-note@-2 {{enum declares raw type 'Int', preventing synthesized conformance of 'NotComparableEnumOne' to 'Comparable'}}
24+
case value
25+
}
26+
27+
// Automatic synthesis of Comparable requires associated values to be Comparable as well.
28+
29+
enum NotComparableEnumTwo: Comparable {
30+
// expected-error@-1 {{type 'NotComparableEnumTwo' does not conform to protocol 'Comparable'}}
31+
struct NotComparable: Equatable {}
32+
case value(NotComparable)
33+
// expected-note@-1 {{associated value type 'NotComparableEnumTwo.NotComparable' does not conform to protocol 'Comparable', preventing synthesized conformance of 'NotComparableEnumTwo' to 'Comparable'}}
34+
}

0 commit comments

Comments
 (0)