Skip to content

Derive Equatable & Hashable for uninhabited types #17756

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
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
42 changes: 34 additions & 8 deletions lib/Sema/DerivedConformanceEquatableHashable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ static bool canDeriveConformance(TypeChecker &tc, DeclContext *DC,
ProtocolDecl *protocol) {
// The type must be an enum or a struct.
if (auto enumDecl = dyn_cast<EnumDecl>(target)) {
// The enum must have cases.
if (!enumDecl->hasCases())
return false;

// The cases must not have associated values, or all associated values must
// conform to the protocol.
return allAssociatedValuesConformToProtocol(tc, DC, enumDecl, protocol);
Expand Down Expand Up @@ -304,6 +300,34 @@ static GuardStmt *returnIfNotEqualGuard(ASTContext &C,
return new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conditions), body);
}

static void
deriveBodyEquatable_enum_uninhabited_eq(AbstractFunctionDecl *eqDecl) {
auto parentDC = eqDecl->getDeclContext();
ASTContext &C = parentDC->getASTContext();

auto args = eqDecl->getParameterLists().back();
auto aParam = args->get(0);
auto bParam = args->get(1);

assert(!cast<EnumDecl>(aParam->getType()->getAnyNominal())->hasCases());

SmallVector<ASTNode, 1> statements;
SmallVector<ASTNode, 0> cases;

// switch (a, b) { }
auto aRef = new (C) DeclRefExpr(aParam, DeclNameLoc(), /*implicit*/ true);
auto bRef = new (C) DeclRefExpr(bParam, DeclNameLoc(), /*implicit*/ true);
auto abExpr = TupleExpr::create(C, SourceLoc(), {aRef, bRef}, {}, {},
SourceLoc(), /*HasTrailingClosure*/ false,
/*implicit*/ true);
auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), abExpr,
SourceLoc(), cases, SourceLoc(), C);
statements.push_back(switchStmt);

auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
eqDecl->setBody(body);
}

/// Derive the body for an '==' operator for an enum that has no associated
/// values. This generates code that converts each value to its integer ordinal
/// and compares them, which produces an optimal single icmp instruction.
Expand Down Expand Up @@ -673,11 +697,13 @@ ValueDecl *DerivedConformance::deriveEquatable(ValueDecl *requirement) {

// Build the necessary decl.
if (requirement->getBaseName() == "==") {
if (auto ED = dyn_cast<EnumDecl>(Nominal)) {
if (auto ed = dyn_cast<EnumDecl>(Nominal)) {
auto bodySynthesizer =
ED->hasOnlyCasesWithoutAssociatedValues()
? &deriveBodyEquatable_enum_noAssociatedValues_eq
: &deriveBodyEquatable_enum_hasAssociatedValues_eq;
!ed->hasCases()
? &deriveBodyEquatable_enum_uninhabited_eq
: ed->hasOnlyCasesWithoutAssociatedValues()
? &deriveBodyEquatable_enum_noAssociatedValues_eq
: &deriveBodyEquatable_enum_hasAssociatedValues_eq;
return deriveEquatable_eq(*this, TC.Context.Id_derived_enum_equals,
bodySynthesizer);
} else if (isa<StructDecl>(Nominal))
Expand Down
12 changes: 6 additions & 6 deletions test/Sema/enum_conformance_synthesis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum CustomHashable {

var hashValue: Int { return 0 }
}
func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 5 {{non-matching type}}
func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 4 {{non-matching type}}
return true
}

Expand All @@ -63,7 +63,7 @@ enum InvalidCustomHashable {

var hashValue: String { return "" } // expected-note{{previously declared here}}
}
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 5 {{non-matching type}}
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 4 {{non-matching type}}
return ""
}
func invalidCustomHashable() {
Expand Down Expand Up @@ -173,8 +173,8 @@ func genericNotHashable() {
GenericNotHashable<String>.A("a").hash(into: &hasher) // expected-error {{value of type 'GenericNotHashable<String>' has no member 'hash'}}
}

// An enum with no cases should not derive conformance.
enum NoCases: Hashable {} // expected-error 2 {{does not conform}}
// An enum with no cases should also derive conformance.
enum NoCases: Hashable {}

// rdar://19773050
private enum Bar<T> {
Expand Down Expand Up @@ -213,7 +213,7 @@ public enum Medicine {

extension Medicine : Equatable {}

public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 5 {{non-matching type}}
public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 4 {{non-matching type}}
return true
}

Expand All @@ -236,7 +236,7 @@ extension NotExplicitlyHashableAndCannotDerive : CaseIterable {} // expected-err
// Verify that conformance (albeit manually implemented) can still be added to
// a type in a different file.
extension OtherFileNonconforming: Hashable {
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 5 {{non-matching type}}
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 4 {{non-matching type}}
return true
}
var hashValue: Int { return 0 }
Expand Down