diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4425b27c80adf..1ca075367899c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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, diff --git a/lib/Sema/DerivedConformanceComparable.cpp b/lib/Sema/DerivedConformanceComparable.cpp index 3e7325ede4b06..3aa74e42527e7 100644 --- a/lib/Sema/DerivedConformanceComparable.cpp +++ b/lib/Sema/DerivedConformanceComparable.cpp @@ -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(nominal)) { + auto contextDecl = DC->getAsDecl(); + ctx.Diags.diagnose( + contextDecl->getLoc(), diag::automatic_protocol_synthesis_unsupported, + comparableProto->getName().str(), isa(contextDecl)); + } + + if (auto *enumDecl = dyn_cast(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()) { + 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()); + } + } +} diff --git a/lib/Sema/DerivedConformanceEquatableHashable.cpp b/lib/Sema/DerivedConformanceEquatableHashable.cpp index fee4e9012b313..fc6741865fe87 100644 --- a/lib/Sema/DerivedConformanceEquatableHashable.cpp +++ b/lib/Sema/DerivedConformanceEquatableHashable.cpp @@ -130,8 +130,8 @@ void diagnoseFailedDerivation(DeclContext *DC, NominalTypeDecl *nominal, if (auto *classDecl = dyn_cast(nominal)) { ctx.Diags.diagnose(classDecl->getLoc(), - diag::classes_automatic_protocol_synthesis, - protocol->getName().str()); + diag::automatic_protocol_synthesis_unsupported, + protocol->getName().str(), 0); } } diff --git a/lib/Sema/DerivedConformances.cpp b/lib/Sema/DerivedConformances.cpp index 1dc13ba25bea5..ba3e66a25b7d8 100644 --- a/lib/Sema/DerivedConformances.cpp +++ b/lib/Sema/DerivedConformances.cpp @@ -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, diff --git a/lib/Sema/DerivedConformances.h b/lib/Sema/DerivedConformances.h index 678c9fc208b3f..de4ea42576949 100644 --- a/lib/Sema/DerivedConformances.h +++ b/lib/Sema/DerivedConformances.h @@ -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 diff --git a/localization/diagnostics/en.yaml b/localization/diagnostics/en.yaml index 952fb8b16f9d5..25811510f4b99 100644 --- a/localization/diagnostics/en.yaml +++ b/localization/diagnostics/en.yaml @@ -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: >- diff --git a/test/Sema/enum_conformance_synthesis.swift b/test/Sema/enum_conformance_synthesis.swift index b823bf99eb021..189d00a3851c6 100644 --- a/test/Sema/enum_conformance_synthesis.swift +++ b/test/Sema/enum_conformance_synthesis.swift @@ -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 { @@ -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. // :0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool' // :0: error: unexpected note produced: candidate has non-matching type ' (Generic, Generic) -> Bool' diff --git a/test/decl/protocol/special/comparable/comparable_supported.swift b/test/decl/protocol/special/comparable/comparable_supported.swift new file mode 100644 index 0000000000000..8910879fcca96 --- /dev/null +++ b/test/decl/protocol/special/comparable/comparable_supported.swift @@ -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 +} diff --git a/test/decl/protocol/special/comparable/comparable_unsupported.swift b/test/decl/protocol/special/comparable/comparable_unsupported.swift new file mode 100644 index 0000000000000..832c3f8b99187 --- /dev/null +++ b/test/decl/protocol/special/comparable/comparable_unsupported.swift @@ -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'}} +}