Skip to content

[Typechecker] Emit specialized diagnostic notes on automatic synthesis failure to Comparable #32797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2833,9 +2833,12 @@ NOTE(missing_member_type_conformance_prevents_synthesis, none,
"protocol %2, preventing synthesized conformance "
"of %3 to %2",
(unsigned, Type, Type, Type))
NOTE(classes_automatic_protocol_synthesis,none,
"automatic synthesis of '%0' is not supported for classes",
(StringRef))
NOTE(automatic_protocol_synthesis_unsupported,none,
"automatic synthesis of '%0' is not supported for %select{classes|structs}1",
(StringRef, unsigned))
NOTE(comparable_synthesis_raw_value_not_allowed, none,
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
(Type, Type, Type))

// Dynamic Self
ERROR(dynamic_self_non_method,none,
Expand Down
37 changes: 37 additions & 0 deletions lib/Sema/DerivedConformanceComparable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,40 @@ ValueDecl *DerivedConformance::deriveComparable(ValueDecl *requirement) {
}
return deriveComparable_lt(*this, synthesizer);
}

void DerivedConformance::tryDiagnoseFailedComparableDerivation(
DeclContext *DC, NominalTypeDecl *nominal) {
auto &ctx = DC->getASTContext();
auto *comparableProto = ctx.getProtocol(KnownProtocolKind::Comparable);
if (!isa<EnumDecl>(nominal)) {
auto contextDecl = DC->getAsDecl();
ctx.Diags.diagnose(
contextDecl->getLoc(), diag::automatic_protocol_synthesis_unsupported,
comparableProto->getName().str(), isa<StructDecl>(contextDecl));
}

if (auto *enumDecl = dyn_cast<EnumDecl>(nominal)) {
auto nonconformingAssociatedTypes =
DerivedConformance::associatedValuesNotConformingToProtocol(
DC, enumDecl, comparableProto);
for (auto *typeToDiagnose : nonconformingAssociatedTypes) {
SourceLoc reprLoc;
if (auto *repr = typeToDiagnose->getTypeRepr())
reprLoc = repr->getStartLoc();
ctx.Diags.diagnose(
reprLoc, diag::missing_member_type_conformance_prevents_synthesis, 0,
typeToDiagnose->getInterfaceType(),
comparableProto->getDeclaredType(),
nominal->getDeclaredInterfaceType());
}

if (enumDecl->hasRawType() && !enumDecl->getRawType()->is<ErrorType>()) {
auto rawType = enumDecl->getRawType();
auto rawTypeLoc = enumDecl->getInherited()[0].getSourceRange().Start;
ctx.Diags.diagnose(rawTypeLoc,
diag::comparable_synthesis_raw_value_not_allowed,
rawType, nominal->getDeclaredInterfaceType(),
comparableProto->getDeclaredType());
}
}
}
4 changes: 2 additions & 2 deletions lib/Sema/DerivedConformanceEquatableHashable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ void diagnoseFailedDerivation(DeclContext *DC, NominalTypeDecl *nominal,

if (auto *classDecl = dyn_cast<ClassDecl>(nominal)) {
ctx.Diags.diagnose(classDecl->getLoc(),
diag::classes_automatic_protocol_synthesis,
protocol->getName().str());
diag::automatic_protocol_synthesis_unsupported,
protocol->getName().str(), 0);
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/DerivedConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ void DerivedConformance::tryDiagnoseFailedDerivation(DeclContext *DC,
if (*knownProtocol == KnownProtocolKind::Hashable) {
tryDiagnoseFailedHashableDerivation(DC, nominal);
}

if (*knownProtocol == KnownProtocolKind::Comparable) {
tryDiagnoseFailedComparableDerivation(DC, nominal);
}
}

ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/DerivedConformances.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ class DerivedConformance {
/// \returns the derived member, which will also be added to the type.
ValueDecl *deriveComparable(ValueDecl *requirement);

/// Diagnose problems, if any, preventing automatic derivation of Comparable
/// requirements
///
/// \param nominal The nominal type for which we would like to diagnose
/// derivation failures
static void tryDiagnoseFailedComparableDerivation(DeclContext *DC,
NominalTypeDecl *nominal);

/// Determine if an Equatable requirement can be derived for a type.
///
/// This is implemented for enums without associated values or all-Equatable
Expand Down
8 changes: 6 additions & 2 deletions localization/diagnostics/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6701,9 +6701,13 @@
%select{associated value|stored property}0 type %1 does not conform to
protocol %2, preventing synthesized conformance of %3 to %2

- id: classes_automatic_protocol_synthesis
- id: automatic_protocol_synthesis_unsupported
msg: >-
automatic synthesis of '%0' is not supported for classes
automatic synthesis of '%0' is not supported for %select{classes|structs}1

- id: comparable_synthesis_raw_value_not_allowed
msg: >-
enum declares raw type %0, preventing synthesized conformance of %1 to %2

- id: dynamic_self_non_method
msg: >-
Expand Down
43 changes: 1 addition & 42 deletions test/Sema/enum_conformance_synthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func genericNotHashable() {
}

// An enum with no cases should also derive conformance.
enum NoCases: Hashable, Comparable {}
enum NoCases: Hashable {}

// rdar://19773050
private enum Bar<T> {
Expand Down Expand Up @@ -327,47 +327,6 @@ enum ImpliedMain: ImplierMain {
}
extension ImpliedOther: ImplierMain {}

// Comparable enum synthesis
enum Angel: Comparable {
case lily, elsa, karlie
}

func pit(_ a: Angel, against b: Angel) -> Bool {
return a < b
}

// enums with non-conforming payloads don’t get synthesized Comparable
enum Notice: Comparable { // expected-error{{type 'Notice' does not conform to protocol 'Comparable'}} expected-error{{type 'Notice' does not conform to protocol 'Equatable'}}
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'}}
}

// neither do enums with raw values
enum Track: Int, Comparable { // expected-error{{type 'Track' does not conform to protocol 'Comparable'}}
case four = 4
case five = 5
case six = 6
}

// synthesized Comparable must be explicit
enum Publicist {
case thow, paine
}

func miss(_ a: Publicist, outsold b: Publicist) -> Bool {
return b < a // expected-error{{binary operator '<' cannot be applied to two 'Publicist' operands}}
}

// can synthesize Comparable conformance through extension
enum Birthyear {
case eighties(Int)
case nineties(Int)
case twothousands(Int)
}
extension Birthyear: Comparable {
}
func canEatHotChip(_ birthyear:Birthyear) -> Bool {
return birthyear > .nineties(3)
}
// FIXME: Remove -verify-ignore-unknown.
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '<T> (Generic<T>, Generic<T>) -> Bool'
Expand Down
27 changes: 27 additions & 0 deletions test/decl/protocol/special/comparable/comparable_supported.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown

// Comparable enum synthesis
enum Angel: Comparable {
case lily, elsa, karlie
}

func pit(_ a: Angel, against b: Angel) -> Bool {
return a < b // Okay
}

// An enum with no cases should also derive conformance to Comparable.

enum NoCasesEnum: Comparable {} // Okay

// Comparable enum conformance through extension
enum Birthyear {
case eighties(Int)
case nineties(Int)
case twothousands(Int)
}

extension Birthyear: Comparable {}

func canEatHotChip(_ birthyear:Birthyear) -> Bool {
return birthyear > .nineties(3) // Okay
}
34 changes: 34 additions & 0 deletions test/decl/protocol/special/comparable/comparable_unsupported.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown

// Automatic synthesis of Comparable is only supported for enums for now.

struct NotComparableStruct: Comparable {
// expected-error@-1 {{type 'NotComparableStruct' does not conform to protocol 'Comparable'}}
// expected-note@-2 {{automatic synthesis of 'Comparable' is not supported for structs}}
var value = 0
}

class NotComparableClass: Comparable {
// expected-error@-1 {{type 'NotComparableClass' does not conform to protocol 'Comparable'}}
// expected-note@-2 {{automatic synthesis of 'Comparable' is not supported for classes}}
// expected-error@-3 {{type 'NotComparableClass' does not conform to protocol 'Equatable'}}
// expected-note@-4 {{automatic synthesis of 'Equatable' is not supported for classes}}
var value = 1
}

// Automatic synthesis of Comparable requires enum without raw type.

enum NotComparableEnumOne: Int, Comparable {
// expected-error@-1 {{type 'NotComparableEnumOne' does not conform to protocol 'Comparable'}}
// expected-note@-2 {{enum declares raw type 'Int', preventing synthesized conformance of 'NotComparableEnumOne' to 'Comparable'}}
case value
}

// Automatic synthesis of Comparable requires associated values to be Comparable as well.

enum NotComparableEnumTwo: Comparable {
// expected-error@-1 {{type 'NotComparableEnumTwo' does not conform to protocol 'Comparable'}}
struct NotComparable: Equatable {}
case value(NotComparable)
// expected-note@-1 {{associated value type 'NotComparableEnumTwo.NotComparable' does not conform to protocol 'Comparable', preventing synthesized conformance of 'NotComparableEnumTwo' to 'Comparable'}}
}