Skip to content

Commit 124251c

Browse files
authored
Merge pull request #9619 from allevato/synthesize-equatable-hashable
Synthesize Equatable/Hashable for complex enums, structs
2 parents ea1307c + ddaa390 commit 124251c

19 files changed

+1534
-234
lines changed

include/swift/AST/ASTContext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,13 @@ class ASTContext {
481481
/// Retrieve the declaration of Swift.==(Int, Int) -> Bool.
482482
FuncDecl *getEqualIntDecl() const;
483483

484+
/// Retrieve the declaration of
485+
/// Swift._mixForSynthesizedHashValue (Int, Int) -> Int.
486+
FuncDecl *getMixForSynthesizedHashValueDecl() const;
487+
488+
/// Retrieve the declaration of Swift._mixInt(Int) -> Int.
489+
FuncDecl *getMixIntDecl() const;
490+
484491
/// Retrieve the declaration of Array.append(element:)
485492
FuncDecl *getArrayAppendElementDecl() const;
486493

include/swift/AST/Decl.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,14 +2949,6 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
29492949
/// conformances.
29502950
void registerProtocolConformance(ProtocolConformance *conformance);
29512951

2952-
/// \brief True if the type can implicitly derive a conformance for the given
2953-
/// protocol.
2954-
///
2955-
/// If true, explicit conformance checking will synthesize implicit
2956-
/// declarations for requirements of the protocol that are not satisfied by
2957-
/// the type's explicit members.
2958-
bool derivesProtocolConformance(ProtocolDecl *protocol) const;
2959-
29602952
void setConformanceLoader(LazyMemberLoader *resolver, uint64_t contextData);
29612953

29622954
/// classifyAsOptionalType - Decide whether this declaration is one

include/swift/AST/DeclContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ namespace swift {
6565
class SerializedPatternBindingInitializer;
6666
class SerializedDefaultArgumentInitializer;
6767
class SerializedTopLevelCodeDecl;
68+
class StructDecl;
6869

6970
enum class DeclContextKind : uint8_t {
7071
AbstractClosureExpr,
@@ -255,6 +256,10 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
255256
/// EnumDecl, otherwise return null.
256257
EnumDecl *getAsEnumOrEnumExtensionContext() const;
257258

259+
/// If this DeclContext is a struct, or an extension on a struct, return the
260+
/// StructDecl, otherwise return null.
261+
StructDecl *getAsStructOrStructExtensionContext() const;
262+
258263
/// If this DeclContext is a protocol, or an extension on a
259264
/// protocol, return the ProtocolDecl, otherwise return null.
260265
ProtocolDecl *getAsProtocolOrProtocolExtensionContext() const;

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ IDENTIFIER_WITH_NAME(NativeClassLayout, "_NativeClass")
116116
IDENTIFIER_WITH_NAME(MatchOperator, "~=")
117117
IDENTIFIER_WITH_NAME(EqualsOperator, "==")
118118
IDENTIFIER_WITH_NAME(derived_enum_equals, "__derived_enum_equals")
119+
IDENTIFIER_WITH_NAME(derived_struct_equals, "__derived_struct_equals")
119120

120121
// Precedence groups
121122
IDENTIFIER(AssignmentPrecedence)

lib/AST/ASTContext.cpp

Lines changed: 136 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL)
192192
/// func ==(Int, Int) -> Bool
193193
FuncDecl *EqualIntDecl = nullptr;
194194

195+
/// func _mixForSynthesizedHashValue(Int, Int) -> Int
196+
FuncDecl *MixForSynthesizedHashValueDecl = nullptr;
197+
198+
/// func _mixInt(Int) -> Int
199+
FuncDecl *MixIntDecl = nullptr;
200+
195201
/// func append(Element) -> void
196202
FuncDecl *ArrayAppendElementDecl = nullptr;
197203

@@ -935,27 +941,78 @@ static bool isBuiltinWordType(Type type) {
935941
return false;
936942
}
937943

938-
FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
939-
if (Impl.GetBoolDecl)
940-
return Impl.GetBoolDecl;
944+
/// Looks up all implementations of an operator (globally and declared in types)
945+
/// and passes potential matches to the given callback. The search stops when
946+
/// the callback returns true (in which case the matching function declaration
947+
/// is returned); otherwise, nullptr is returned if there are no matches.
948+
/// \p C The AST context.
949+
/// \p oper The name of the operator.
950+
/// \p contextType If the operator is declared on a type, then only operators
951+
/// defined on this type should be considered.
952+
/// \p callback A callback that takes as its two arguments the input type and
953+
/// result type of a candidate function declaration and returns true if the
954+
/// function matches the desired criteria.
955+
/// \return The matching function declaration, or nullptr if there was no match.
956+
template <int ExpectedCandidateCount, typename MatchFuncCallback>
957+
static FuncDecl *lookupOperatorFunc(const ASTContext &ctx, StringRef oper,
958+
Type contextType,
959+
MatchFuncCallback &callback) {
960+
SmallVector<ValueDecl *, ExpectedCandidateCount> candidates;
961+
ctx.lookupInSwiftModule(oper, candidates);
962+
963+
for (auto candidate : candidates) {
964+
// All operator declarations should be functions, but make sure.
965+
auto *funcDecl = dyn_cast<FuncDecl>(candidate);
966+
if (!funcDecl)
967+
continue;
941968

942-
// Look for the function.
943-
Type input, output;
944-
auto decl = findLibraryIntrinsic(*this, "_getBool", resolver);
945-
if (!decl ||
946-
!isNonGenericIntrinsic(decl, /*allowTypeMembers=*/false, input, output))
947-
return nullptr;
969+
if (funcDecl->getDeclContext()->isTypeContext()) {
970+
auto contextTy = funcDecl->getDeclContext()->getDeclaredInterfaceType();
971+
if (!contextTy->isEqual(contextType)) continue;
972+
}
948973

949-
// Input must be Builtin.Int1
950-
if (!isBuiltinInt1Type(input))
951-
return nullptr;
974+
if (auto resolver = ctx.getLazyResolver())
975+
resolver->resolveDeclSignature(funcDecl);
952976

953-
// Output must be a global type named Bool.
954-
if (!output->isEqual(getBoolDecl()->getDeclaredType()))
977+
Type inputType, resultType;
978+
if (!isNonGenericIntrinsic(funcDecl, /*allowTypeMembers=*/true, inputType,
979+
resultType))
980+
continue;
981+
982+
if (callback(inputType, resultType))
983+
return funcDecl;
984+
}
985+
986+
return nullptr;
987+
}
988+
989+
/// Looks up the implementation (assumed to be singular) of a globally-defined
990+
/// standard library intrinsic function and passes the potential match to the
991+
/// given callback if it was found. If the callback returns true, then the
992+
/// match is returned; otherwise, nullptr is returned.
993+
/// \p ctx The AST context.
994+
/// \p name The name of the function.
995+
/// \p resolver The lazy resolver.
996+
/// \p callback A callback that takes as its two arguments the input type and
997+
/// result type of the candidate function declaration and returns true if
998+
/// the function matches the desired criteria.
999+
/// \return The matching function declaration, or nullptr if there was no match.
1000+
template <typename MatchFuncCallback>
1001+
static FuncDecl *lookupLibraryIntrinsicFunc(const ASTContext &ctx,
1002+
StringRef name,
1003+
LazyResolver *resolver,
1004+
MatchFuncCallback &callback) {
1005+
Type inputType, resultType;
1006+
auto decl = findLibraryIntrinsic(ctx, name, resolver);
1007+
if (!decl ||
1008+
!isNonGenericIntrinsic(decl, /*allowTypeMembers=*/false, inputType,
1009+
resultType))
9551010
return nullptr;
9561011

957-
Impl.GetBoolDecl = decl;
958-
return decl;
1012+
if (callback(inputType, resultType))
1013+
return decl;
1014+
1015+
return nullptr;
9591016
}
9601017

9611018
FuncDecl *ASTContext::getEqualIntDecl() const {
@@ -967,45 +1024,74 @@ FuncDecl *ASTContext::getEqualIntDecl() const {
9671024

9681025
auto intType = getIntDecl()->getDeclaredType();
9691026
auto boolType = getBoolDecl()->getDeclaredType();
970-
SmallVector<ValueDecl *, 30> equalFuncs;
971-
lookupInSwiftModule("==", equalFuncs);
972-
973-
// Find the overload for Int.
974-
for (ValueDecl *vd : equalFuncs) {
975-
// All "==" decls should be functions, but who knows...
976-
auto *funcDecl = dyn_cast<FuncDecl>(vd);
977-
if (!funcDecl)
978-
continue;
1027+
auto callback = [&](Type inputType, Type resultType) {
1028+
// Check for the signature: (Int, Int) -> Bool
1029+
auto tupleType = dyn_cast<TupleType>(inputType.getPointer());
1030+
assert(tupleType);
1031+
return tupleType->getNumElements() == 2 &&
1032+
tupleType->getElementType(0)->isEqual(intType) &&
1033+
tupleType->getElementType(1)->isEqual(intType) &&
1034+
resultType->isEqual(boolType);
1035+
};
9791036

980-
if (funcDecl->getDeclContext()->isTypeContext()) {
981-
auto contextTy = funcDecl->getDeclContext()->getDeclaredInterfaceType();
982-
if (!contextTy->isEqual(intType)) continue;
983-
}
1037+
auto decl = lookupOperatorFunc<32>(*this, "==", intType, callback);
1038+
Impl.EqualIntDecl = decl;
1039+
return decl;
1040+
}
9841041

985-
if (auto resolver = getLazyResolver())
986-
resolver->resolveDeclSignature(funcDecl);
1042+
FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
1043+
if (Impl.GetBoolDecl)
1044+
return Impl.GetBoolDecl;
9871045

988-
Type input, resultType;
989-
if (!isNonGenericIntrinsic(funcDecl, /*allowTypeMembers=*/true, input,
990-
resultType))
991-
continue;
992-
993-
// Check for the signature: (Int, Int) -> Bool
994-
auto tupleType = dyn_cast<TupleType>(input.getPointer());
1046+
auto callback = [&](Type inputType, Type resultType) {
1047+
// Look for the signature (Builtin.Int1) -> Bool
1048+
return isBuiltinInt1Type(inputType) &&
1049+
resultType->isEqual(getBoolDecl()->getDeclaredType());
1050+
};
1051+
1052+
auto decl = lookupLibraryIntrinsicFunc(*this, "_getBool", resolver, callback);
1053+
Impl.GetBoolDecl = decl;
1054+
return decl;
1055+
}
1056+
1057+
FuncDecl *ASTContext::getMixForSynthesizedHashValueDecl() const {
1058+
if (Impl.MixForSynthesizedHashValueDecl)
1059+
return Impl.MixForSynthesizedHashValueDecl;
1060+
1061+
auto resolver = getLazyResolver();
1062+
auto intType = getIntDecl()->getDeclaredType();
1063+
1064+
auto callback = [&](Type inputType, Type resultType) {
1065+
// Look for the signature (Int, Int) -> Int
1066+
auto tupleType = dyn_cast<TupleType>(inputType.getPointer());
9951067
assert(tupleType);
996-
if (tupleType->getNumElements() != 2)
997-
continue;
1068+
return tupleType->getNumElements() == 2 &&
1069+
tupleType->getElementType(0)->isEqual(intType) &&
1070+
tupleType->getElementType(1)->isEqual(intType) &&
1071+
resultType->isEqual(intType);
1072+
};
9981073

999-
auto argType1 = tupleType->getElementType(0);
1000-
auto argType2 = tupleType->getElementType(1);
1001-
if (argType1->isEqual(intType) &&
1002-
argType2->isEqual(intType) &&
1003-
resultType->isEqual(boolType)) {
1004-
Impl.EqualIntDecl = funcDecl;
1005-
return funcDecl;
1006-
}
1007-
}
1008-
return nullptr;
1074+
auto decl = lookupLibraryIntrinsicFunc(
1075+
*this, "_mixForSynthesizedHashValue", resolver, callback);
1076+
Impl.MixForSynthesizedHashValueDecl = decl;
1077+
return decl;
1078+
}
1079+
1080+
FuncDecl *ASTContext::getMixIntDecl() const {
1081+
if (Impl.MixIntDecl)
1082+
return Impl.MixIntDecl;
1083+
1084+
auto resolver = getLazyResolver();
1085+
auto intType = getIntDecl()->getDeclaredType();
1086+
1087+
auto callback = [&](Type inputType, Type resultType) {
1088+
// Look for the signature (Int) -> Int
1089+
return inputType->isEqual(intType) && resultType->isEqual(intType);
1090+
};
1091+
1092+
auto decl = lookupLibraryIntrinsicFunc(*this, "_mixInt", resolver, callback);
1093+
Impl.MixIntDecl = decl;
1094+
return decl;
10091095
}
10101096

10111097
FuncDecl *ASTContext::getArrayAppendElementDecl() const {

lib/AST/Decl.cpp

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2264,72 +2264,6 @@ bool NominalTypeDecl::hasFixedLayout(ModuleDecl *M,
22642264
llvm_unreachable("bad resilience expansion");
22652265
}
22662266

2267-
2268-
bool NominalTypeDecl::derivesProtocolConformance(ProtocolDecl *protocol) const {
2269-
// Only known protocols can be derived.
2270-
auto knownProtocol = protocol->getKnownProtocolKind();
2271-
if (!knownProtocol)
2272-
return false;
2273-
2274-
if (auto *enumDecl = dyn_cast<EnumDecl>(this)) {
2275-
switch (*knownProtocol) {
2276-
// The presence of a raw type is an explicit declaration that
2277-
// the compiler should derive a RawRepresentable conformance.
2278-
case KnownProtocolKind::RawRepresentable:
2279-
return enumDecl->hasRawType();
2280-
2281-
// Enums without associated values can implicitly derive Equatable and
2282-
// Hashable conformance.
2283-
case KnownProtocolKind::Equatable:
2284-
case KnownProtocolKind::Hashable:
2285-
return enumDecl->hasCases()
2286-
&& enumDecl->hasOnlyCasesWithoutAssociatedValues();
2287-
2288-
// @objc enums can explicitly derive their _BridgedNSError conformance.
2289-
case KnownProtocolKind::BridgedNSError:
2290-
return isObjC() && enumDecl->hasCases()
2291-
&& enumDecl->hasOnlyCasesWithoutAssociatedValues();
2292-
2293-
// Enums without associated values and enums with a raw type of String
2294-
// or Int can explicitly derive CodingKey conformance.
2295-
case KnownProtocolKind::CodingKey: {
2296-
Type rawType = enumDecl->getRawType();
2297-
if (rawType) {
2298-
auto parentDC = enumDecl->getDeclContext();
2299-
ASTContext &C = parentDC->getASTContext();
2300-
2301-
auto nominal = rawType->getAnyNominal();
2302-
return nominal == C.getStringDecl() || nominal == C.getIntDecl();
2303-
}
2304-
2305-
// hasOnlyCasesWithoutAssociatedValues will return true for empty enums;
2306-
// empty enumas are allowed to conform as well.
2307-
return enumDecl->hasOnlyCasesWithoutAssociatedValues();
2308-
}
2309-
2310-
default:
2311-
return false;
2312-
}
2313-
} else if (isa<StructDecl>(this) || isa<ClassDecl>(this)) {
2314-
// Structs and classes can explicitly derive Encodable and Decodable
2315-
// conformance (explicitly meaning we can synthesize an implementation if
2316-
// a type conforms manually).
2317-
if (*knownProtocol == KnownProtocolKind::Encodable ||
2318-
*knownProtocol == KnownProtocolKind::Decodable) {
2319-
// FIXME: This is not actually correct. We cannot promise to always
2320-
// provide a witness here for all structs and classes. Unfortunately,
2321-
// figuring out whether this is actually possible requires much more
2322-
// context -- a TypeChecker and the parent decl context at least -- and is
2323-
// tightly coupled to the logic within DerivedConformance.
2324-
// This unfortunately means that we expect a witness even if one will not
2325-
// be produced, which requires DerivedConformance::deriveCodable to output
2326-
// its own diagnostics.
2327-
return true;
2328-
}
2329-
}
2330-
return false;
2331-
}
2332-
23332267
void NominalTypeDecl::computeType() {
23342268
ASTContext &ctx = getASTContext();
23352269

lib/AST/DeclContext.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ EnumDecl *DeclContext::getAsEnumOrEnumExtensionContext() const {
9292
return dyn_cast_or_null<EnumDecl>(getAsTypeOrTypeExtensionContext());
9393
}
9494

95+
StructDecl *DeclContext::getAsStructOrStructExtensionContext() const {
96+
return dyn_cast_or_null<StructDecl>(getAsTypeOrTypeExtensionContext());
97+
}
98+
9599
ProtocolDecl *DeclContext::getAsProtocolOrProtocolExtensionContext() const {
96100
return dyn_cast_or_null<ProtocolDecl>(getAsTypeOrTypeExtensionContext());
97101
}

0 commit comments

Comments
 (0)