Skip to content

[Typechecker] Fix a crasher involving RawValue not being Equatable #27050

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

Closed
wants to merge 6 commits into from
Closed
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
17 changes: 17 additions & 0 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,23 @@ static void checkEnumRawValues(TypeChecker &TC, EnumDecl *ED) {
KnownProtocolKind::ExpressibleByExtendedGraphemeClusterLiteral,
};

// The raw type must conform to Equatable, unless we explicitly have a
// rawValue and init(rawValue:).
if (!conformsToProtocol(KnownProtocolKind::Equatable)) {
auto &ctx = TC.Context;
auto rawValueInit =
DeclName(ctx, DeclBaseName::createConstructor(), {ctx.Id_rawValue});
auto doesNotHaveRawValue = ED->lookupDirect(ctx.Id_rawValue).empty();
auto doesNotHaveRawValueInit = ED->lookupDirect(rawValueInit).empty();
Copy link
Contributor

Choose a reason for hiding this comment

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

lookupDirect isn't necessarily correct anyway, since that won't look into extensions. If you go through the normal qualified lookup path maybe we'll get the synthesis we need anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm I thought it did when I looked at the implementation, but alright, I'll do it the other way!


if (doesNotHaveRawValue || doesNotHaveRawValueInit) {
auto rawTypeInherited = ED->getInherited().front();
TC.diagnose(rawTypeInherited.getLoc(), diag::enum_raw_type_not_equatable,
rawTy);
rawTypeInherited.setInvalidType(TC.Context);
}
}

if (conformsToProtocol(KnownProtocolKind::ExpressibleByIntegerLiteral)) {
valueKind = AutomaticEnumValueKind::Integer;
} else if (conformsToProtocol(KnownProtocolKind::ExpressibleByStringLiteral)){
Expand Down
35 changes: 18 additions & 17 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3018,13 +3018,28 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
if (auto derivable = DerivedConformance::getDerivableRequirement(
nominal,
requirement)) {
auto derivableProto = cast<ProtocolDecl>(derivable->getDeclContext());
if (derivable == requirement) {
// If it's the same requirement, we can derive it here.
canDerive = true;
// If it's the same requirement, we can derive it here. Although, don't
// attempt to do it if we have an explicit witness. If they don't match,
// we'll diagnose them later. If they do match, there's nothing to derive.
//
// If the decl is synthesized, then don't attempt the check. Otherwise,
// we could end up with issues like someone adding a intValue property to
// Decodable enum, which will satisfy the intValue requirement of nested
// CodingKey enum, which is not correct.
//
// FIXME: The lookup check isn't enough for Equatable
bool isEquatable = derivableProto->getKnownProtocolKind() ==
KnownProtocolKind::Equatable;
if (nominal->isImplicit() || isEquatable) {
canDerive = true;
} else {
canDerive = nominal->lookupDirect(derivable->getFullName()).empty();
}
} else {
// Otherwise, go satisfy the derivable requirement, which can introduce
// a member that could in turn satisfy *this* requirement.
auto derivableProto = cast<ProtocolDecl>(derivable->getDeclContext());
if (auto conformance =
TypeChecker::conformsToProtocol(Adoptee, derivableProto,
DC, None)) {
Expand Down Expand Up @@ -4001,20 +4016,6 @@ static void diagnoseConformanceFailure(Type T,
diags.diagnose(enumDecl->getInherited()[0].getSourceRange().Start,
diag::enum_raw_type_nonconforming_and_nonsynthable,
T, rawType);

// If the reason is that the raw type does not conform to
// Equatable, say so.
auto equatableProto = ctx.getProtocol(KnownProtocolKind::Equatable);
if (!equatableProto)
return;

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

return;
}
}
Expand Down
18 changes: 18 additions & 0 deletions test/Generics/inheritance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,21 @@ enum SE3<T : ExpressibleByIntegerLiteral> : T where T: InstanceGettable {
}

enum SE4<T : ExpressibleByIntegerLiteral & Equatable> : T { case X }

// SR-6897
struct SR_6897_S: ExpressibleByStringLiteral {
init(stringLiteral: String) {}
}

enum SR_6897_E1: SR_6897_S { // expected-error {{RawRepresentable conformance cannot be synthesized because raw type 'SR_6897_S' is not Equatable}}
case some1
typealias RawValue = SR_6897_S
}

enum SR_6897_E2: SR_6897_S { // expected-error {{'SR_6897_E2' declares raw type 'SR_6897_S', but does not conform to RawRepresentable and conformance could not be synthesized}}
// expected-note@-1 2{{do you want to add protocol stubs?}}
case some2
typealias RawValue = SR_6897_S
init?(rawValue: Int) { self = .some2 } // expected-note {{candidate has non-matching type '(rawValue: Int)'}}
var rawValue: Int { 0 } // expected-note {{candidate has non-matching type 'Int'}}
}
11 changes: 4 additions & 7 deletions test/Sema/enum_conformance_synthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,10 @@ func customHashable() {
CustomHashable.A.hash(into: &hasher)
}

// We still synthesize conforming overloads of '==' and 'hashValue' if
// explicit definitions don't satisfy the protocol requirements. Probably
// not what we actually want.
enum InvalidCustomHashable {
enum InvalidCustomHashable { // expected-error {{type 'InvalidCustomHashable' does not conform to protocol 'Hashable'}} // expected-note {{do you want to add protocol stubs?}}
case A, B

var hashValue: String { return "" } // expected-note{{previously declared here}}
var hashValue: String { return "" } // expected-note 2{{candidate has non-matching type 'String'}}
}
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String {
return ""
Expand All @@ -71,8 +68,8 @@ func invalidCustomHashable() {
var s: String = InvalidCustomHashable.A == .B
s = InvalidCustomHashable.A.hashValue
_ = s
var _: Int = InvalidCustomHashable.A.hashValue
InvalidCustomHashable.A.hash(into: &hasher)
var _: Int = InvalidCustomHashable.A.hashValue // expected-error {{cannot convert value of type 'String' to specified type 'Int'}}
InvalidCustomHashable.A.hash(into: &hasher) // expected-error {{value of type 'InvalidCustomHashable' has no member 'hash'}}
}

// Check use of an enum's synthesized members before the enum is actually declared.
Expand Down
17 changes: 17 additions & 0 deletions validation-test/compiler_crashers_2_fixed/sr6897.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: not %target-swift-frontend -typecheck %s

struct Foo: ExpressibleByStringLiteral {
init(stringLiteral: String) {}
}

enum Bar1: Foo {
case some1
typealias RawValue = Foo
}

enum Bar2: Foo {
case some2
typealias RawValue = Foo
init?(rawValue: Int) { self = .some2 }
var rawValue: Int { 0 }
}