Skip to content

Synthesize Equatable/Hashable for complex enums, structs #9619

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
merged 27 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eda0e38
[AST] Accessors for ^= and _mixInt
allevato May 9, 2017
a825a31
[AST] Add identifier for synthesized struct equals
allevato May 11, 2017
6dfa87d
[AST] Add DeclContext.getAsStructOrStructExtensionContext
allevato May 12, 2017
4777f9d
[AST] Enable derived Equatable/Hashable for structs
allevato May 12, 2017
425de49
[Sema] Synthesize Eq/Hash for enums with payloads, structs
allevato May 9, 2017
5f39659
[test] Update enum Equatable/Hashable tests
allevato May 12, 2017
aa93ee5
Formatting and other cleanup.
allevato May 16, 2017
1e1bed3
[AST] Factor out common code in operator/func lookups
allevato May 16, 2017
a3db968
Move derivesProtocolConformances from AST to Sema
allevato May 17, 2017
fd93e03
[Sema] Forbid synthesis in an extension
allevato May 20, 2017
fca78a3
[Sema] Fix type checking for enums defined in other files
allevato Jun 2, 2017
d9827ac
[Sema] inout/ASTNode fixes after rebasing
allevato Aug 9, 2017
8419f10
Fix typo in _mixInt comment
allevato Aug 9, 2017
59bbaa5
[test] Add tests for indirect enums
allevato Aug 9, 2017
1d5ef70
Fix some typos
allevato Aug 21, 2017
f377597
[Sema] Apply review feedback
allevato Sep 9, 2017
3731382
Merge branch 'master' into synthesize-equatable-hashable
allevato Sep 9, 2017
acbf8f7
Improve synthesized member correctness tests
allevato Sep 22, 2017
f2c434a
Merge branch 'master' into synthesize-equatable-hashable
allevato Sep 22, 2017
aef3d09
"yet" was removed from some diagnostics by 21b2073b
allevato Sep 22, 2017
3618164
[stdlib] Add _mixForSynthesizedHashValue to stdlib
allevato Sep 24, 2017
13ad81a
Synthed hashValue now calls _mixForSynthesizedHashValue
allevato Sep 24, 2017
de93a8d
[test] Fix executable_test requirement
allevato Sep 24, 2017
f74b3fb
[Sema] Only emit warnings for implicit self use in getters
allevato Sep 25, 2017
a30c218
[test] Move executable tests into test/Interpreter
allevato Oct 9, 2017
715ba63
Merge branch 'master' into synthesize-equatable-hashable
allevato Oct 9, 2017
ddaa390
Merge branch 'master' into synthesize-equatable-hashable
allevato Oct 10, 2017
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
7 changes: 7 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ class ASTContext {
/// Retrieve the declaration of Swift.==(Int, Int) -> Bool.
FuncDecl *getEqualIntDecl() const;

/// Retrieve the declaration of
/// Swift._mixForSynthesizedHashValue (Int, Int) -> Int.
FuncDecl *getMixForSynthesizedHashValueDecl() const;

/// Retrieve the declaration of Swift._mixInt(Int) -> Int.
FuncDecl *getMixIntDecl() const;

/// Retrieve the declaration of Array.append(element:)
FuncDecl *getArrayAppendElementDecl() const;

Expand Down
8 changes: 0 additions & 8 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2949,14 +2949,6 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
/// conformances.
void registerProtocolConformance(ProtocolConformance *conformance);

/// \brief True if the type can implicitly derive a conformance for the given
/// protocol.
///
/// If true, explicit conformance checking will synthesize implicit
/// declarations for requirements of the protocol that are not satisfied by
/// the type's explicit members.
bool derivesProtocolConformance(ProtocolDecl *protocol) const;

void setConformanceLoader(LazyMemberLoader *resolver, uint64_t contextData);

/// classifyAsOptionalType - Decide whether this declaration is one
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ namespace swift {
class SerializedPatternBindingInitializer;
class SerializedDefaultArgumentInitializer;
class SerializedTopLevelCodeDecl;
class StructDecl;

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

/// If this DeclContext is a struct, or an extension on a struct, return the
/// StructDecl, otherwise return null.
StructDecl *getAsStructOrStructExtensionContext() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to do a pass over callers of getAsNominalTypeOrNominalTypeExtensionContext() and see if any just dyn_cast<StructDecl> the result and clean them up.

Copy link
Member Author

Choose a reason for hiding this comment

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

I found 98 instances of getAsNominalTypeOrNominalTypeExtensionContext, but none of them appear to unconditionally cast to StructDecl afterward.


/// If this DeclContext is a protocol, or an extension on a
/// protocol, return the ProtocolDecl, otherwise return null.
ProtocolDecl *getAsProtocolOrProtocolExtensionContext() const;
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ IDENTIFIER_WITH_NAME(NativeClassLayout, "_NativeClass")
IDENTIFIER_WITH_NAME(MatchOperator, "~=")
IDENTIFIER_WITH_NAME(EqualsOperator, "==")
IDENTIFIER_WITH_NAME(derived_enum_equals, "__derived_enum_equals")
IDENTIFIER_WITH_NAME(derived_struct_equals, "__derived_struct_equals")

// Precedence groups
IDENTIFIER(AssignmentPrecedence)
Expand Down
186 changes: 136 additions & 50 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL)
/// func ==(Int, Int) -> Bool
FuncDecl *EqualIntDecl = nullptr;

/// func _mixForSynthesizedHashValue(Int, Int) -> Int
FuncDecl *MixForSynthesizedHashValueDecl = nullptr;

/// func _mixInt(Int) -> Int
FuncDecl *MixIntDecl = nullptr;

/// func append(Element) -> void
FuncDecl *ArrayAppendElementDecl = nullptr;

Expand Down Expand Up @@ -936,27 +942,78 @@ static bool isBuiltinWordType(Type type) {
return false;
}

FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
if (Impl.GetBoolDecl)
return Impl.GetBoolDecl;
/// Looks up all implementations of an operator (globally and declared in types)
/// and passes potential matches to the given callback. The search stops when
/// the callback returns true (in which case the matching function declaration
/// is returned); otherwise, nullptr is returned if there are no matches.
/// \p C The AST context.
/// \p oper The name of the operator.
/// \p contextType If the operator is declared on a type, then only operators
/// defined on this type should be considered.
/// \p callback A callback that takes as its two arguments the input type and
/// result type of a candidate function declaration and returns true if the
/// function matches the desired criteria.
/// \return The matching function declaration, or nullptr if there was no match.
template <int ExpectedCandidateCount, typename MatchFuncCallback>
static FuncDecl *lookupOperatorFunc(const ASTContext &ctx, StringRef oper,
Type contextType,
MatchFuncCallback &callback) {
SmallVector<ValueDecl *, ExpectedCandidateCount> candidates;
ctx.lookupInSwiftModule(oper, candidates);

for (auto candidate : candidates) {
// All operator declarations should be functions, but make sure.
auto *funcDecl = dyn_cast<FuncDecl>(candidate);
if (!funcDecl)
continue;

// Look for the function.
Type input, output;
auto decl = findLibraryIntrinsic(*this, "_getBool", resolver);
if (!decl ||
!isNonGenericIntrinsic(decl, /*allowTypeMembers=*/false, input, output))
return nullptr;
if (funcDecl->getDeclContext()->isTypeContext()) {
auto contextTy = funcDecl->getDeclContext()->getDeclaredInterfaceType();
if (!contextTy->isEqual(contextType)) continue;
}

// Input must be Builtin.Int1
if (!isBuiltinInt1Type(input))
return nullptr;
if (auto resolver = ctx.getLazyResolver())
resolver->resolveDeclSignature(funcDecl);

// Output must be a global type named Bool.
if (!output->isEqual(getBoolDecl()->getDeclaredType()))
Type inputType, resultType;
if (!isNonGenericIntrinsic(funcDecl, /*allowTypeMembers=*/true, inputType,
resultType))
continue;

if (callback(inputType, resultType))
return funcDecl;
}

return nullptr;
}

/// Looks up the implementation (assumed to be singular) of a globally-defined
/// standard library intrinsic function and passes the potential match to the
/// given callback if it was found. If the callback returns true, then the
/// match is returned; otherwise, nullptr is returned.
/// \p ctx The AST context.
/// \p name The name of the function.
/// \p resolver The lazy resolver.
/// \p callback A callback that takes as its two arguments the input type and
/// result type of the candidate function declaration and returns true if
/// the function matches the desired criteria.
/// \return The matching function declaration, or nullptr if there was no match.
template <typename MatchFuncCallback>
static FuncDecl *lookupLibraryIntrinsicFunc(const ASTContext &ctx,
StringRef name,
LazyResolver *resolver,
MatchFuncCallback &callback) {
Type inputType, resultType;
auto decl = findLibraryIntrinsic(ctx, name, resolver);
if (!decl ||
!isNonGenericIntrinsic(decl, /*allowTypeMembers=*/false, inputType,
resultType))
return nullptr;

Impl.GetBoolDecl = decl;
return decl;
if (callback(inputType, resultType))
return decl;

return nullptr;
}

FuncDecl *ASTContext::getEqualIntDecl() const {
Expand All @@ -968,45 +1025,74 @@ FuncDecl *ASTContext::getEqualIntDecl() const {

auto intType = getIntDecl()->getDeclaredType();
auto boolType = getBoolDecl()->getDeclaredType();
SmallVector<ValueDecl *, 30> equalFuncs;
lookupInSwiftModule("==", equalFuncs);

// Find the overload for Int.
for (ValueDecl *vd : equalFuncs) {
// All "==" decls should be functions, but who knows...
auto *funcDecl = dyn_cast<FuncDecl>(vd);
if (!funcDecl)
continue;
auto callback = [&](Type inputType, Type resultType) {
// Check for the signature: (Int, Int) -> Bool
auto tupleType = dyn_cast<TupleType>(inputType.getPointer());
assert(tupleType);
return tupleType->getNumElements() == 2 &&
tupleType->getElementType(0)->isEqual(intType) &&
tupleType->getElementType(1)->isEqual(intType) &&
resultType->isEqual(boolType);
};

if (funcDecl->getDeclContext()->isTypeContext()) {
auto contextTy = funcDecl->getDeclContext()->getDeclaredInterfaceType();
if (!contextTy->isEqual(intType)) continue;
}
auto decl = lookupOperatorFunc<32>(*this, "==", intType, callback);
Impl.EqualIntDecl = decl;
return decl;
}

if (auto resolver = getLazyResolver())
resolver->resolveDeclSignature(funcDecl);
FuncDecl *ASTContext::getGetBoolDecl(LazyResolver *resolver) const {
if (Impl.GetBoolDecl)
return Impl.GetBoolDecl;

Type input, resultType;
if (!isNonGenericIntrinsic(funcDecl, /*allowTypeMembers=*/true, input,
resultType))
continue;

// Check for the signature: (Int, Int) -> Bool
auto tupleType = dyn_cast<TupleType>(input.getPointer());
auto callback = [&](Type inputType, Type resultType) {
// Look for the signature (Builtin.Int1) -> Bool
return isBuiltinInt1Type(inputType) &&
resultType->isEqual(getBoolDecl()->getDeclaredType());
};

auto decl = lookupLibraryIntrinsicFunc(*this, "_getBool", resolver, callback);
Impl.GetBoolDecl = decl;
return decl;
}

FuncDecl *ASTContext::getMixForSynthesizedHashValueDecl() const {
if (Impl.MixForSynthesizedHashValueDecl)
return Impl.MixForSynthesizedHashValueDecl;

auto resolver = getLazyResolver();
auto intType = getIntDecl()->getDeclaredType();

auto callback = [&](Type inputType, Type resultType) {
// Look for the signature (Int, Int) -> Int
auto tupleType = dyn_cast<TupleType>(inputType.getPointer());
assert(tupleType);
if (tupleType->getNumElements() != 2)
continue;
return tupleType->getNumElements() == 2 &&
tupleType->getElementType(0)->isEqual(intType) &&
tupleType->getElementType(1)->isEqual(intType) &&
resultType->isEqual(intType);
};

auto argType1 = tupleType->getElementType(0);
auto argType2 = tupleType->getElementType(1);
if (argType1->isEqual(intType) &&
argType2->isEqual(intType) &&
resultType->isEqual(boolType)) {
Impl.EqualIntDecl = funcDecl;
return funcDecl;
}
}
return nullptr;
auto decl = lookupLibraryIntrinsicFunc(
*this, "_mixForSynthesizedHashValue", resolver, callback);
Impl.MixForSynthesizedHashValueDecl = decl;
return decl;
}

FuncDecl *ASTContext::getMixIntDecl() const {
if (Impl.MixIntDecl)
return Impl.MixIntDecl;

auto resolver = getLazyResolver();
auto intType = getIntDecl()->getDeclaredType();

auto callback = [&](Type inputType, Type resultType) {
// Look for the signature (Int) -> Int
return inputType->isEqual(intType) && resultType->isEqual(intType);
};

auto decl = lookupLibraryIntrinsicFunc(*this, "_mixInt", resolver, callback);
Impl.MixIntDecl = decl;
return decl;
}

FuncDecl *ASTContext::getArrayAppendElementDecl() const {
Expand Down
66 changes: 0 additions & 66 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2264,72 +2264,6 @@ bool NominalTypeDecl::hasFixedLayout(ModuleDecl *M,
llvm_unreachable("bad resilience expansion");
}


bool NominalTypeDecl::derivesProtocolConformance(ProtocolDecl *protocol) const {
// Only known protocols can be derived.
auto knownProtocol = protocol->getKnownProtocolKind();
if (!knownProtocol)
return false;

if (auto *enumDecl = dyn_cast<EnumDecl>(this)) {
switch (*knownProtocol) {
// The presence of a raw type is an explicit declaration that
// the compiler should derive a RawRepresentable conformance.
case KnownProtocolKind::RawRepresentable:
return enumDecl->hasRawType();

// Enums without associated values can implicitly derive Equatable and
// Hashable conformance.
case KnownProtocolKind::Equatable:
case KnownProtocolKind::Hashable:
return enumDecl->hasCases()
&& enumDecl->hasOnlyCasesWithoutAssociatedValues();

// @objc enums can explicitly derive their _BridgedNSError conformance.
case KnownProtocolKind::BridgedNSError:
return isObjC() && enumDecl->hasCases()
&& enumDecl->hasOnlyCasesWithoutAssociatedValues();

// Enums without associated values and enums with a raw type of String
// or Int can explicitly derive CodingKey conformance.
case KnownProtocolKind::CodingKey: {
Type rawType = enumDecl->getRawType();
if (rawType) {
auto parentDC = enumDecl->getDeclContext();
ASTContext &C = parentDC->getASTContext();

auto nominal = rawType->getAnyNominal();
return nominal == C.getStringDecl() || nominal == C.getIntDecl();
}

// hasOnlyCasesWithoutAssociatedValues will return true for empty enums;
// empty enumas are allowed to conform as well.
return enumDecl->hasOnlyCasesWithoutAssociatedValues();
}

default:
return false;
}
} else if (isa<StructDecl>(this) || isa<ClassDecl>(this)) {
// Structs and classes can explicitly derive Encodable and Decodable
// conformance (explicitly meaning we can synthesize an implementation if
// a type conforms manually).
if (*knownProtocol == KnownProtocolKind::Encodable ||
*knownProtocol == KnownProtocolKind::Decodable) {
// FIXME: This is not actually correct. We cannot promise to always
// provide a witness here for all structs and classes. Unfortunately,
// figuring out whether this is actually possible requires much more
// context -- a TypeChecker and the parent decl context at least -- and is
// tightly coupled to the logic within DerivedConformance.
// This unfortunately means that we expect a witness even if one will not
// be produced, which requires DerivedConformance::deriveCodable to output
// its own diagnostics.
return true;
}
}
return false;
}

void NominalTypeDecl::computeType() {
ASTContext &ctx = getASTContext();

Expand Down
4 changes: 4 additions & 0 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ EnumDecl *DeclContext::getAsEnumOrEnumExtensionContext() const {
return dyn_cast_or_null<EnumDecl>(getAsTypeOrTypeExtensionContext());
}

StructDecl *DeclContext::getAsStructOrStructExtensionContext() const {
return dyn_cast_or_null<StructDecl>(getAsTypeOrTypeExtensionContext());
}

ProtocolDecl *DeclContext::getAsProtocolOrProtocolExtensionContext() const {
return dyn_cast_or_null<ProtocolDecl>(getAsTypeOrTypeExtensionContext());
}
Expand Down
Loading