Skip to content
Permalink
Browse files

Derive Equatable & Hashable for uninhabited types

  • Loading branch information...
mdiep committed Jul 5, 2018
1 parent 823d63b commit 30949c75ef8d745626112b7a0f94eca3e26aab04
Showing with 40 additions and 14 deletions.
  1. +34 −8 lib/Sema/DerivedConformanceEquatableHashable.cpp
  2. +6 −6 test/Sema/enum_conformance_synthesis.swift
@@ -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);
@@ -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.
@@ -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))
@@ -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
}

@@ -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() {
@@ -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> {
@@ -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
}

@@ -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 }

0 comments on commit 30949c7

Please sign in to comment.
You can’t perform that action at this time.