Skip to content

Fix SR-2134: emit 'rawValue' bodies for enums with a generic raw value #4038

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 2 commits into from
Aug 24, 2016
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
5 changes: 5 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,11 @@ ERROR(raw_type_not_literal_convertible,none,
ERROR(enum_raw_type_not_equatable,none,
"RawRepresentable 'init' cannot be synthesized because raw type %0 is not "
"Equatable", (Type))
ERROR(enum_raw_type_nonconforming_and_nonsynthable,none,
"%0 declares raw type %1, but does not conform to RawRepresentable "
"and conformance could not be synthesized", (Type, Type))
NOTE(enum_declares_rawrep_with_raw_type,none,
"%0 declares raw type %1, which implies RawRepresentable", (Type, Type))
ERROR(enum_raw_type_access,none,
"enum %select{must be declared "
"%select{private|fileprivate|internal|PUBLIC}2"
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2031,8 +2031,8 @@ bool NominalTypeDecl::derivesProtocolConformance(ProtocolDecl *protocol) const {

if (auto *enumDecl = dyn_cast<EnumDecl>(this)) {
switch (*knownProtocol) {
// Enums with raw types can implicitly derive their RawRepresentable
// conformance.
// The presence of a raw type is an explicit declaration that
// the compiler should derive a RawRepresentable conformance.
case KnownProtocolKind::RawRepresentable:
return enumDecl->hasRawType();

Expand Down
1 change: 1 addition & 0 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ GenericParamList *DeclContext::getGenericParamsOfContext() const {
}
llvm_unreachable("bad DeclContextKind");
}
llvm_unreachable("unknown parent");
}

GenericSignature *DeclContext::getGenericSignatureOfContext() const {
Expand Down
12 changes: 8 additions & 4 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,10 +606,14 @@ void NominalTypeDecl::prepareConformanceTable() const {
if (resolver)
resolver->resolveRawType(theEnum);
if (theEnum->hasRawType()) {
if (auto rawRepresentable = getASTContext().getProtocol(
KnownProtocolKind::RawRepresentable)) {
ConformanceTable->addSynthesizedConformance(mutableThis,
rawRepresentable);
if (auto rawRepresentable = ctx.getProtocol(KnownProtocolKind::RawRepresentable)) {

// The presence of a raw type is an explicit declaration that
// the compiler should derive a RawRepresentable conformance.
auto conformance = ctx.getConformance(mutableThis->getDeclaredTypeInContext(), rawRepresentable,
mutableThis->getNameLoc(), mutableThis->getInnermostDeclContext(),
ProtocolConformanceState::Incomplete);
ConformanceTable->registerProtocolConformance(conformance);
}
}
}
Expand Down
116 changes: 64 additions & 52 deletions lib/Sema/DerivedConformanceRawRepresentable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ static void deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl) {

Type rawTy = enumDecl->getRawType();
assert(rawTy);
rawTy = ArchetypeBuilder::mapTypeIntoContext(toRawDecl, rawTy);

for (auto elt : enumDecl->getAllElements()) {
if (!elt->getTypeCheckedRawValueExpr() ||
!elt->getTypeCheckedRawValueExpr()->getType()->isEqual(rawTy)) {
return;
}
assert(elt->getTypeCheckedRawValueExpr() &&
"Enum element has no literal - missing a call to checkEnumRawValues()");
assert(elt->getTypeCheckedRawValueExpr()->getType()->isEqual(rawTy));
}

Type enumType = parentDC->getDeclaredTypeInContext();
Expand Down Expand Up @@ -186,10 +187,9 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl) {
rawTy = ArchetypeBuilder::mapTypeIntoContext(initDecl, rawTy);

for (auto elt : enumDecl->getAllElements()) {
if (!elt->getTypeCheckedRawValueExpr() ||
!elt->getTypeCheckedRawValueExpr()->getType()->isEqual(rawTy)) {
return;
}
assert(elt->getTypeCheckedRawValueExpr() &&
"Enum element has no literal - missing a call to checkEnumRawValues()");
assert(elt->getTypeCheckedRawValueExpr()->getType()->isEqual(rawTy));
}

Type enumType = parentDC->getDeclaredTypeInContext();
Expand Down Expand Up @@ -257,18 +257,11 @@ static ConstructorDecl *deriveRawRepresentable_init(TypeChecker &tc,
auto rawType = ArchetypeBuilder::mapTypeIntoContext(parentDC,
rawInterfaceType);

// Make sure that the raw type is Equatable. We need it to ensure that we have
// a suitable ~= for the switch.
auto equatableProto = tc.getProtocol(enumDecl->getLoc(),
KnownProtocolKind::Equatable);
if (!equatableProto)
return nullptr;

if (!tc.conformsToProtocol(rawType, equatableProto, enumDecl, None)) {
SourceLoc loc = enumDecl->getInherited()[0].getSourceRange().Start;
tc.diagnose(loc, diag::enum_raw_type_not_equatable, rawType);
return nullptr;
}
assert(equatableProto);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will emit an unused variable warning if asserts are off.

Add a (void) equatableProto; right after to silence this (and confirm by compiling with ./build-script -r)

assert(tc.conformsToProtocol(rawType, equatableProto, enumDecl, None));
(void)equatableProto;

Type enumType = parentDC->getDeclaredTypeInContext();
auto *selfDecl = ParamDecl::createUnboundSelf(SourceLoc(), parentDC,
Expand Down Expand Up @@ -353,31 +346,62 @@ static ConstructorDecl *deriveRawRepresentable_init(TypeChecker &tc,
return initDecl;
}

ValueDecl *DerivedConformance::deriveRawRepresentable(TypeChecker &tc,
Decl *parentDecl,
NominalTypeDecl *type,
ValueDecl *requirement) {
// Check preconditions. These should already have been diagnosed by
// type-checking but we may still get here after recovery.

// The type must be an enum.
auto enumDecl = dyn_cast<EnumDecl>(type);
if (!enumDecl)
return nullptr;

static bool canSynthesizeRawRepresentable(TypeChecker &tc, Decl *parentDecl, EnumDecl *enumDecl) {

// It must have a valid raw type.
if (!enumDecl->hasRawType())
return nullptr;
Type rawType = enumDecl->getRawType();
if (!rawType)
return false;
auto parentDC = cast<DeclContext>(parentDecl);
rawType = ArchetypeBuilder::mapTypeIntoContext(parentDC, rawType);

if (!enumDecl->getInherited().empty() &&
enumDecl->getInherited().front().isError())
return nullptr;
return false;

// The raw type must be Equatable, so that we have a suitable ~= for synthesized switch statements.
auto equatableProto = tc.getProtocol(enumDecl->getLoc(),
KnownProtocolKind::Equatable);
if (!equatableProto)
return false;

if (!tc.conformsToProtocol(rawType, equatableProto, enumDecl, None)) {
SourceLoc loc = enumDecl->getInherited()[0].getSourceRange().Start;
tc.diagnose(loc, diag::enum_raw_type_not_equatable, rawType);
return false;
}

// There must be enum elements.
if (enumDecl->getAllElements().empty())
return nullptr;
return false;

for (auto elt : enumDecl->getAllElements())
// Have the type-checker validate that:
// - the enum elements all have the same type
// - they all match the enum type
for (auto elt : enumDecl->getAllElements()) {
tc.validateDecl(elt);
if (elt->isInvalid()) {
return false;
}
}

// If it meets all of those requirements, we can synthesize RawRepresentable conformance.
return true;
}

ValueDecl *DerivedConformance::deriveRawRepresentable(TypeChecker &tc,
Decl *parentDecl,
NominalTypeDecl *type,
ValueDecl *requirement) {

// We can only synthesize RawRepresentable for enums.
auto enumDecl = dyn_cast<EnumDecl>(type);
if (!enumDecl)
return nullptr;

// Check other preconditions for synthesized conformance.
if (!canSynthesizeRawRepresentable(tc, parentDecl, enumDecl))
return nullptr;

if (requirement->getName() == tc.Context.Id_rawValue)
return deriveRawRepresentable_raw(tc, parentDecl, enumDecl);
Expand All @@ -394,27 +418,15 @@ Type DerivedConformance::deriveRawRepresentable(TypeChecker &tc,
Decl *parentDecl,
NominalTypeDecl *type,
AssociatedTypeDecl *assocType) {
// Check preconditions. These should already have been diagnosed by
// type-checking but we may still get here after recovery.

// The type must be an enum.

// We can only synthesize RawRepresentable for enums.
auto enumDecl = dyn_cast<EnumDecl>(type);
if (!enumDecl)
return nullptr;

// It must have a valid raw type.
if (!enumDecl->hasRawType())
return nullptr;
if (!enumDecl->getInherited().empty() &&
enumDecl->getInherited().front().isError())
return nullptr;

// There must be enum elements.
if (enumDecl->getAllElements().empty())
return nullptr;

for (auto elt : enumDecl->getAllElements())
tc.validateDecl(elt);
// Check other preconditions for synthesized conformance.
if (!canSynthesizeRawRepresentable(tc, parentDecl, enumDecl))
return nullptr;

if (assocType->getName() == tc.Context.Id_RawValue) {
return deriveRawRepresentable_Raw(tc, parentDecl, enumDecl);
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2569,6 +2569,7 @@ static void checkEnumRawValues(TypeChecker &TC, EnumDecl *ED) {
diag::enum_with_raw_type_case_with_argument);
TC.diagnose(ED->getInherited().front().getSourceRange().Start,
diag::enum_raw_type_here, rawTy);
elt->setInvalid();
continue;
}

Expand All @@ -2587,6 +2588,7 @@ static void checkEnumRawValues(TypeChecker &TC, EnumDecl *ED) {
// is the first element.
auto nextValue = getAutomaticRawValueExpr(TC, valueKind, elt, prevValue);
if (!nextValue) {
elt->setInvalid();
break;
}
elt->setRawValueExpr(nextValue);
Expand Down
27 changes: 26 additions & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3924,13 +3924,25 @@ static void diagnoseConformanceFailure(TypeChecker &TC, Type T,
return;
}

// As a special case, diagnose conversion to ExpressibleByNilLiteral, since we
// Special case: diagnose conversion to ExpressibleByNilLiteral, since we
// know this is something involving 'nil'.
if (Proto->isSpecificProtocol(KnownProtocolKind::ExpressibleByNilLiteral)) {
TC.diagnose(ComplainLoc, diag::cannot_use_nil_with_this_type, T);
return;
}

// Special case: for enums with a raw type, explain that the failing
// conformance to RawRepresentable was inferred.
if (auto enumDecl = T->getEnumOrBoundGenericEnum()) {
if (Proto->isSpecificProtocol(KnownProtocolKind::RawRepresentable) &&
enumDecl->derivesProtocolConformance(Proto) && enumDecl->hasRawType()) {

TC.diagnose(enumDecl->getInherited()[0].getSourceRange().Start,
diag::enum_raw_type_nonconforming_and_nonsynthable,
T, enumDecl->getRawType());
return;
}
}

TC.diagnose(ComplainLoc, diag::type_does_not_conform,
T, Proto->getDeclaredType());
Expand Down Expand Up @@ -4718,6 +4730,19 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
dc->getDeclaredTypeInContext(),
diag.Protocol->getName());

// Special case: explain that 'RawRepresentable' conformance
// is implied for enums which already declare a raw type.
if (auto enumDecl = dyn_cast<EnumDecl>(existingDecl)) {
if (diag.Protocol->isSpecificProtocol(KnownProtocolKind::RawRepresentable) &&
enumDecl->derivesProtocolConformance(diag.Protocol) &&
enumDecl->hasRawType()) {
diagnose(enumDecl->getInherited()[0].getSourceRange().Start,
diag::enum_declares_rawrep_with_raw_type,
dc->getDeclaredTypeInContext(), enumDecl->getRawType());
continue;
}
}

diagnose(existingDecl, diag::declared_protocol_conformance_here,
dc->getDeclaredTypeInContext(),
static_cast<unsigned>(diag.ExistingKind),
Expand Down
18 changes: 12 additions & 6 deletions test/Generics/inheritance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,27 @@ func testGenericInherit() {


struct SS<T> : T { } // expected-error{{inheritance from non-protocol type 'T'}}
enum SE<T> : T { case X } // expected-error{{raw type 'T' is not expressible by any literal}}
// expected-error@-1{{type 'SE<T>' does not conform to protocol 'RawRepresentable'}}
enum SE<T> : T { case X } // expected-error{{raw type 'T' is not expressible by any literal}} expected-error {{SE<T>' declares raw type 'T', but does not conform to RawRepresentable and conformance could not be synthesized}}

// Also need Equatable for init?(RawValue)
enum SE2<T : ExpressibleByIntegerLiteral>
: T // expected-error{{RawRepresentable 'init' cannot be synthesized because raw type 'T' is not Equatable}}
enum SE2<T : ExpressibleByIntegerLiteral>
: T // expected-error {{'SE2<T>' declares raw type 'T', but does not conform to RawRepresentable and conformance could not be synthesized}} expected-error{{RawRepresentable 'init' cannot be synthesized because raw type 'T' is not Equatable}}
{ case X }

// ... but not if init?(RawValue) is directly implemented some other way.
enum SE3<T : ExpressibleByIntegerLiteral> : T {
// ... but not if init?(RawValue) and `rawValue` are directly implemented some other way.
protocol InstanceGettable {
static var someInstance : Self { get }
}
enum SE3<T : ExpressibleByIntegerLiteral> : T where T: InstanceGettable {
case X

init?(rawValue: T) {
self = SE3.X
}

var rawValue : T {
return T.someInstance
}
}

enum SE4<T : ExpressibleByIntegerLiteral & Equatable> : T { case X }
6 changes: 3 additions & 3 deletions test/IDE/print_ast_tc_decls_errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,17 @@ enum EnumWithInheritance2 : FooNonExistentProtocol, BarNonExistentProtocol {} //
// TYREPR: {{^}}enum EnumWithInheritance2 : FooNonExistentProtocol, BarNonExistentProtocol {{{$}}

enum EnumWithInheritance3 : FooClass { case X } // expected-error {{raw type 'FooClass' is not expressible by any literal}}
// expected-error@-1{{type 'EnumWithInheritance3' does not conform to protocol 'RawRepresentable'}}
// expected-error@-1{{'EnumWithInheritance3' declares raw type 'FooClass', but does not conform to RawRepresentable and conformance could not be synthesized}}
// NO-TYREPR: {{^}}enum EnumWithInheritance3 : <<error type>> {{{$}}
// TYREPR: {{^}}enum EnumWithInheritance3 : FooClass {{{$}}

enum EnumWithInheritance4 : FooClass, FooProtocol { case X } // expected-error {{raw type 'FooClass' is not expressible by any literal}}
// expected-error@-1{{type 'EnumWithInheritance4' does not conform to protocol 'RawRepresentable'}}
// expected-error@-1{{'EnumWithInheritance4' declares raw type 'FooClass', but does not conform to RawRepresentable and conformance could not be synthesized}}
// NO-TYREPR: {{^}}enum EnumWithInheritance4 : <<error type>>, FooProtocol {{{$}}
// TYREPR: {{^}}enum EnumWithInheritance4 : FooClass, FooProtocol {{{$}}

enum EnumWithInheritance5 : FooClass, BarClass { case X } // expected-error {{raw type 'FooClass' is not expressible by any literal}} expected-error {{multiple enum raw types 'FooClass' and 'BarClass'}}
// expected-error@-1{{type 'EnumWithInheritance5' does not conform to protocol 'RawRepresentable'}}
// expected-error@-1{{'EnumWithInheritance5' declares raw type 'FooClass', but does not conform to RawRepresentable and conformance could not be synthesized}}
// NO-TYREPR: {{^}}enum EnumWithInheritance5 : <<error type>>, <<error type>> {{{$}}
// TYREPR: {{^}}enum EnumWithInheritance5 : FooClass, BarClass {{{$}}

Expand Down
2 changes: 0 additions & 2 deletions test/IRGen/enum_derived.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,3 @@ enum Phantom<T> : Int64 {
case Up
case Down
}

extension Phantom : RawRepresentable {}
Loading