Skip to content

Commit 6054fc1

Browse files
committed
[Sema] Allow Hashable to be implemented by defining either hashValue or _hash(into:)
This removes the default implementation of _hash(into:), and replaces it with automatic synthesis built into the compiler. Hashable can now be implemented by defining either hashValue or _hash(into:) -- the compiler supplies the missing half automatically, in all cases. To determine which _hash(into:) implementation to generate, the synthesizer resolves hashValue -- if it finds a synthesized definition for it, the generated _hash(into:) body implements hashing from scratch, feeding components into the hasher. Otherwise, the body implements _hash(into:) in terms of hashValue. This change introduces some diagnostic regressions, and also requires a followup change in the CoreFoundation overlay.
1 parent eb21719 commit 6054fc1

File tree

3 files changed

+105
-78
lines changed

3 files changed

+105
-78
lines changed

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -838,22 +838,7 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl,
838838
FunctionType::ExtInfo());
839839
}
840840
hashDecl->setInterfaceType(interfaceType);
841-
842-
if (typeDecl->getFormalAccess() != AccessLevel::Private) {
843-
hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl);
844-
} else {
845-
// FIXME: We want to call copyFormalAccessAndVersionedAttrFrom here, but we
846-
// can't copy the access level of a private type, because of the backhanded
847-
// way we synthesize _hash(into:) in deriveHashable -- we need to make sure
848-
// the resolver will find the new function, so it needs to be at least
849-
// fileprivate. (The access level of synthesized members doesn't normally
850-
// matter; they don't go through an access level check after being returned
851-
// from the synthesizer.)
852-
hashDecl->setAccess(AccessLevel::FilePrivate);
853-
if (typeDecl->getAttrs().hasAttribute<VersionedAttr>()) {
854-
hashDecl->getAttrs().add(new (C) VersionedAttr(/*implicit=*/true));
855-
}
856-
}
841+
hashDecl->copyFormalAccessAndVersionedAttrFrom(typeDecl);
857842

858843
// If we aren't synthesizing into an imported/derived type, the derived conformance is
859844
// either from the type itself or an extension, in which case we will emit the
@@ -873,6 +858,30 @@ deriveHashable_hashInto(TypeChecker &tc, Decl *parentDecl,
873858
return hashDecl;
874859
}
875860

861+
/// Derive the body for the _hash(into:) method when hashValue has a
862+
/// user-supplied implementation.
863+
static void
864+
deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) {
865+
// func _hash(into hasher: inout _Hasher) {
866+
// hasher.append(self.hashValue)
867+
// }
868+
auto parentDC = hashIntoDecl->getDeclContext();
869+
ASTContext &C = parentDC->getASTContext();
870+
871+
auto selfDecl = hashIntoDecl->getImplicitSelfDecl();
872+
auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
873+
/*implicit*/ true);
874+
auto hashValueExpr = new (C) UnresolvedDotExpr(selfRef, SourceLoc(),
875+
C.Id_hashValue, DeclNameLoc(),
876+
/*implicit*/ true);
877+
auto hasherParam = hashIntoDecl->getParameterList(1)->get(0);
878+
auto hasherExpr = createHasherAppendCall(C, hasherParam, hashValueExpr);
879+
880+
auto body = BraceStmt::create(C, SourceLoc(), {ASTNode(hasherExpr)},
881+
SourceLoc(), /*implicit*/ true);
882+
hashIntoDecl->setBody(body);
883+
}
884+
876885
/// Derive the body for the '_hash(into:)' method for an enum.
877886
static void
878887
deriveBodyHashable_enum_hashInto(AbstractFunctionDecl *hashIntoDecl) {
@@ -1139,19 +1148,12 @@ deriveHashable_hashValue(TypeChecker &tc, Decl *parentDecl,
11391148
return hashValueDecl;
11401149
}
11411150

1142-
bool DerivedConformance::canDeriveHashable(TypeChecker &tc,
1143-
NominalTypeDecl *type,
1144-
ValueDecl *requirement) {
1145-
auto hashableProto = tc.Context.getProtocol(KnownProtocolKind::Hashable);
1146-
return canDeriveConformance(tc, type, hashableProto);
1147-
}
1148-
11491151
static ValueDecl *
1150-
getHashIntoRequirement(ASTContext &C) {
1152+
getHashValueRequirement(ASTContext &C) {
11511153
auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable);
11521154
for (auto member: hashableProto->getMembers()) {
1153-
if (auto fd = dyn_cast<FuncDecl>(member)) {
1154-
if (fd->getBaseName() == C.Id_hash)
1155+
if (auto fd = dyn_cast<VarDecl>(member)) {
1156+
if (fd->getBaseName() == C.Id_hashValue)
11551157
return fd;
11561158
}
11571159
}
@@ -1171,57 +1173,76 @@ getHashableConformance(Decl *parentDecl) {
11711173
return nullptr;
11721174
}
11731175

1176+
bool DerivedConformance::canDeriveHashable(TypeChecker &tc,
1177+
NominalTypeDecl *type,
1178+
ValueDecl *requirement) {
1179+
if (!isa<EnumDecl>(type) && !isa<StructDecl>(type) && !isa<ClassDecl>(type))
1180+
return false;
1181+
// FIXME: This is not actually correct. We cannot promise to always
1182+
// provide a witness here in all cases. Unfortunately, figuring out
1183+
// whether this is actually possible requires a parent decl context.
1184+
// When the answer is no, DerivedConformance::deriveHashable will output
1185+
// its own diagnostics.
1186+
return true;
1187+
}
1188+
11741189
ValueDecl *DerivedConformance::deriveHashable(TypeChecker &tc,
11751190
Decl *parentDecl,
11761191
NominalTypeDecl *type,
11771192
ValueDecl *requirement) {
11781193
ASTContext &C = parentDecl->getASTContext();
1179-
auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable);
1180-
1181-
// Conformance can't be synthesized in an extension; we allow it as a special
1182-
// case for enums with no associated values to preserve source compatibility.
1183-
auto theEnum = dyn_cast<EnumDecl>(type);
1184-
if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) &&
1185-
type != parentDecl) {
1186-
auto hashableType = hashableProto->getDeclaredType();
1187-
tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension,
1188-
hashableType);
1189-
return nullptr;
1190-
}
11911194

11921195
// var hashValue: Int
11931196
if (requirement->getBaseName() == C.Id_hashValue) {
1194-
auto hashValueDecl = deriveHashable_hashValue(tc, parentDecl, type);
1195-
1196-
// Also derive _hash(into:) -- it has a default implementation, so we
1197-
// wouldn't otherwise consider it as a candidate for synthesizing.
1198-
//
1199-
// FIXME: This assumes that _hash(into:) hasn't already been resolved. It
1200-
// would be nicer to remove the default implementation and independently
1201-
// synthesize either/both of the two Hashable requirements as needed;
1202-
// however, synthesizing methods (as opposed to properties) into imported
1203-
// types is not yet fully supported.
1204-
if (auto hashIntoReq = getHashIntoRequirement(C)) {
1205-
AbstractFunctionDecl::BodySynthesizer hashIntoSynthesizer;
1197+
// We always allow hashValue to be synthesized; invalid cases are diagnosed
1198+
// during _hash(into:) synthesis.
1199+
return deriveHashable_hashValue(tc, parentDecl, type);
1200+
}
1201+
1202+
// Hashable._hash(into:)
1203+
if (requirement->getBaseName() == C.Id_hash) {
1204+
// Start by resolving hashValue conformance.
1205+
auto hashValueReq = getHashValueRequirement(C);
1206+
auto conformance = getHashableConformance(parentDecl);
1207+
auto hashValueDecl = conformance->getWitnessDecl(hashValueReq, &tc);
1208+
1209+
if (hashValueDecl->isImplicit()) {
1210+
// Neither hashValue nor _hash(into:) is explicitly defined; we need to do
1211+
// a full Hashable derivation.
1212+
1213+
// Refuse to synthesize Hashable if type isn't a struct or enum, or if it
1214+
// has non-Hashable stored properties/associated values.
1215+
auto hashableProto = C.getProtocol(KnownProtocolKind::Hashable);
1216+
if (!canDeriveConformance(tc, type, hashableProto)) {
1217+
tc.diagnose(parentDecl->getLoc(), diag::type_does_not_conform,
1218+
type->getDeclaredType(), hashableProto->getDeclaredType());
1219+
return nullptr;
1220+
}
1221+
// Hashable can't be fully synthesized in an extension; we allow it as a
1222+
// special case for enums with no associated values to preserve source
1223+
// compatibility.
1224+
auto theEnum = dyn_cast<EnumDecl>(type);
1225+
if (!(theEnum && theEnum->hasOnlyCasesWithoutAssociatedValues()) &&
1226+
type != parentDecl) {
1227+
tc.diagnose(parentDecl->getLoc(), diag::cannot_synthesize_in_extension,
1228+
hashableProto->getDeclaredType());
1229+
return nullptr;
1230+
}
12061231
if (theEnum)
1207-
hashIntoSynthesizer = &deriveBodyHashable_enum_hashInto;
1232+
return deriveHashable_hashInto(tc, parentDecl, theEnum,
1233+
&deriveBodyHashable_enum_hashInto);
12081234
else if (auto theStruct = dyn_cast<StructDecl>(type))
1209-
hashIntoSynthesizer = &deriveBodyHashable_struct_hashInto;
1235+
return deriveHashable_hashInto(tc, parentDecl, theStruct,
1236+
&deriveBodyHashable_struct_hashInto);
12101237
else
12111238
llvm_unreachable("Attempt to derive Hashable for a type other "
1212-
"than a struct or enum");
1213-
auto hashIntoDecl = deriveHashable_hashInto(tc, parentDecl, type,
1214-
hashIntoSynthesizer);
1215-
if (hashIntoDecl) {
1216-
auto conformance = getHashableConformance(parentDecl);
1217-
auto witnessDecl = conformance->getWitnessDecl(hashIntoReq, &tc);
1218-
if (witnessDecl != hashIntoDecl) {
1219-
tc.diagnose(requirement->getLoc(),
1220-
diag::broken_hashable_requirement);
1221-
}
1222-
}
1239+
"than a struct or enum");
1240+
} else {
1241+
// We can always derive _hash(into:) if hashValue has an explicit
1242+
// implementation.
1243+
return deriveHashable_hashInto(tc, parentDecl, type,
1244+
&deriveBodyHashable_compat_hashInto);
12231245
}
1224-
return hashValueDecl;
12251246
}
12261247

12271248
tc.diagnose(requirement->getLoc(), diag::broken_hashable_requirement);

lib/Sema/DerivedConformances.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,24 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc,
3131
if (!knownProtocol)
3232
return false;
3333

34+
if (*knownProtocol == KnownProtocolKind::Hashable) {
35+
// We can always complete a partial Hashable implementation, and we can
36+
// synthesize a full Hashable implementation for structs and enums with
37+
// Hashable components.
38+
return canDeriveHashable(tc, nominal, protocol);
39+
}
40+
3441
if (auto *enumDecl = dyn_cast<EnumDecl>(nominal)) {
3542
switch (*knownProtocol) {
3643
// The presence of a raw type is an explicit declaration that
3744
// the compiler should derive a RawRepresentable conformance.
3845
case KnownProtocolKind::RawRepresentable:
3946
return enumDecl->hasRawType();
4047

41-
// Enums without associated values can implicitly derive Equatable and
42-
// Hashable conformance.
48+
// Enums without associated values can implicitly derive Equatable
49+
// conformance.
4350
case KnownProtocolKind::Equatable:
4451
return canDeriveEquatable(tc, enumDecl, protocol);
45-
case KnownProtocolKind::Hashable:
46-
return canDeriveHashable(tc, enumDecl, protocol);
4752

4853
// @objc enums can explicitly derive their _BridgedNSError conformance.
4954
case KnownProtocolKind::BridgedNSError:
@@ -87,13 +92,11 @@ bool DerivedConformance::derivesProtocolConformance(TypeChecker &tc,
8792
return true;
8893
}
8994

90-
// Structs can explicitly derive Equatable and Hashable conformance.
95+
// Structs can explicitly derive Equatable conformance.
9196
if (auto structDecl = dyn_cast<StructDecl>(nominal)) {
9297
switch (*knownProtocol) {
9398
case KnownProtocolKind::Equatable:
9499
return canDeriveEquatable(tc, structDecl, protocol);
95-
case KnownProtocolKind::Hashable:
96-
return canDeriveHashable(tc, structDecl, protocol);
97100
default:
98101
return false;
99102
}
@@ -162,6 +165,13 @@ ValueDecl *DerivedConformance::getDerivableRequirement(TypeChecker &tc,
162165
return getRequirement(KnownProtocolKind::Encodable);
163166
}
164167

168+
// Hashable._hash(into: _UnsafeHasher) -> _UnsafeHasher
169+
if (name.isCompoundName() && name.getBaseName() == ctx.Id_hash) {
170+
auto argumentNames = name.getArgumentNames();
171+
if (argumentNames.size() == 1 && argumentNames[0] == ctx.Id_into)
172+
return getRequirement(KnownProtocolKind::Hashable);
173+
}
174+
165175
return nullptr;
166176
}
167177

stdlib/public/core/Hashable.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,12 @@ public protocol Hashable : Equatable {
110110
var hashValue: Int { get }
111111

112112
/// Feed bits to be hashed into the hash function represented by `hasher`.
113+
///
114+
/// If this requirement is not explicitly implemented, the compiler
115+
/// automatically synthesizes an implementation for it.
113116
func _hash(into hasher: inout _Hasher)
114117
}
115118

116-
extension Hashable {
117-
@inline(__always)
118-
public func _hash(into hasher: inout _Hasher) {
119-
hasher.append(self.hashValue)
120-
}
121-
}
122-
123119
// Called by synthesized `hashValue` implementations.
124120
@inline(__always)
125121
public func _hashValue<H: Hashable>(for value: H) -> Int {

0 commit comments

Comments
 (0)