Skip to content

Commit d4906ac

Browse files
committed
Rationalize Implicit Member Synthesis Somewhat
Codable's deep magic currently forces conformance checks in the middle of name lookup in order to inject CodingKeys into lookup results. This is compounded by the fact that this lookup fixup is occuring incrementally, meaning depending on order of requirements being looked up, Decl::getMembers() will give you a different answer. Compounding this, NameLookup relied on the LazyResolver to formalize this layering violation, and relied on implicit laziness to guard against re-entrancy. The approach is multi-pronged: 1) Shift the layering violation into the request evaluator 2) Spell out the kinds of resolution we support explicitly (make them easier to find and kill) 3) Remove the LazyResolver entrypoint this was relying on 4) Split off the property wrappers part into its own utility
1 parent 34ad0eb commit d4906ac

File tree

11 files changed

+188
-82
lines changed

11 files changed

+188
-82
lines changed

include/swift/AST/ASTTypeIDZone.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
SWIFT_TYPEID(AncestryFlags)
1919
SWIFT_TYPEID(CtorInitializerKind)
2020
SWIFT_TYPEID(GenericSignature)
21+
SWIFT_TYPEID(ImplicitMemberAction)
2122
SWIFT_TYPEID(ParamSpecifier)
2223
SWIFT_TYPEID(PropertyWrapperBackingPropertyInfo)
2324
SWIFT_TYPEID(PropertyWrapperTypeInfo)

include/swift/AST/ASTTypeIDs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class TypeAliasDecl;
5555
class Type;
5656
struct TypePair;
5757
enum class AncestryFlags : uint8_t;
58+
enum class ImplicitMemberAction : uint8_t;
5859

5960
// Define the AST type zone (zone 1)
6061
#define SWIFT_TYPEID_ZONE AST

include/swift/AST/Decl.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,16 @@ class alignas(1 << DeclAlignInBits) Decl {
477477
IsDebuggerAlias : 1
478478
);
479479

480-
SWIFT_INLINE_BITFIELD(NominalTypeDecl, GenericTypeDecl, 1+1,
480+
SWIFT_INLINE_BITFIELD(NominalTypeDecl, GenericTypeDecl, 1+1+1,
481481
/// Whether we have already added implicitly-defined initializers
482482
/// to this declaration.
483483
AddedImplicitInitializers : 1,
484484

485485
/// Whether there is are lazily-loaded conformances for this nominal type.
486-
HasLazyConformances : 1
486+
HasLazyConformances : 1,
487+
488+
/// Whether this nominal type is having its semantic members resolved.
489+
IsComputingSemanticMembers : 1
487490
);
488491

489492
SWIFT_INLINE_BITFIELD_FULL(ProtocolDecl, NominalTypeDecl, 1+1+1+1+1+1+1+2+1+1+8+16,
@@ -3328,6 +3331,7 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
33283331
Bits.NominalTypeDecl.AddedImplicitInitializers = false;
33293332
ExtensionGeneration = 0;
33303333
Bits.NominalTypeDecl.HasLazyConformances = false;
3334+
Bits.NominalTypeDecl.IsComputingSemanticMembers = false;
33313335
}
33323336

33333337
friend class ProtocolType;
@@ -3475,6 +3479,8 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
34753479
/// declaration, or \c nullptr if it doesn't have one.
34763480
ConstructorDecl *getDefaultInitializer() const;
34773481

3482+
void synthesizeSemanticMembersIfNeeded(DeclName member);
3483+
34783484
// Implement isa/cast/dyncast/etc.
34793485
static bool classof(const Decl *D) {
34803486
return D->getKind() >= DeclKind::First_NominalTypeDecl &&

include/swift/AST/LazyResolver.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ class LazyResolver {
5252
/// the given protocol conformance.
5353
virtual void resolveWitness(const NormalProtocolConformance *conformance,
5454
ValueDecl *requirement) = 0;
55-
56-
/// Resolve an implicitly-generated member with the given name.
57-
virtual void resolveImplicitMember(NominalTypeDecl *nominal, DeclName member) = 0;
5855
};
5956

6057
class LazyMemberLoader;

include/swift/AST/TypeCheckRequests.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,6 +1668,35 @@ class InheritsSuperclassInitializersRequest
16681668
void cacheResult(bool value) const;
16691669
};
16701670

1671+
// The actions this request takes are all huge layering violations.
1672+
//
1673+
// Please do not add any more.
1674+
enum class ImplicitMemberAction : uint8_t {
1675+
ResolveImplicitInit,
1676+
ResolveCodingKeys,
1677+
ResolveEncodable,
1678+
ResolveDecodable,
1679+
};
1680+
1681+
class ResolveImplicitMemberRequest
1682+
: public SimpleRequest<ResolveImplicitMemberRequest,
1683+
bool(NominalTypeDecl *, ImplicitMemberAction),
1684+
CacheKind::Uncached> {
1685+
public:
1686+
using SimpleRequest::SimpleRequest;
1687+
1688+
private:
1689+
friend SimpleRequest;
1690+
1691+
// Evaluation.
1692+
llvm::Expected<bool> evaluate(Evaluator &evaluator, NominalTypeDecl *NTD,
1693+
ImplicitMemberAction action) const;
1694+
1695+
public:
1696+
// Separate caching.
1697+
bool isCached() const { return true; }
1698+
};
1699+
16711700
// Allow AnyValue to compare two Type values, even though Type doesn't
16721701
// support ==.
16731702
template<>
@@ -1689,6 +1718,7 @@ AnyValue::Holder<GenericSignature>::equals(const HolderBase &other) const {
16891718

16901719
void simple_display(llvm::raw_ostream &out, Type value);
16911720
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
1721+
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);
16921722

16931723
#define SWIFT_TYPEID_ZONE TypeChecker
16941724
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ SWIFT_REQUEST(TypeChecker, HasUserDefinedDesignatedInitRequest,
174174
bool(NominalTypeDecl *), Cached, NoLocationInfo)
175175
SWIFT_REQUEST(TypeChecker, HasMemberwiseInitRequest,
176176
bool(StructDecl *), Cached, NoLocationInfo)
177+
SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
178+
bool(NominalTypeDecl *, ImplicitMemberAction), Uncached,
179+
NoLocationInfo)
177180
SWIFT_REQUEST(TypeChecker, SynthesizeMemberwiseInitRequest,
178181
ConstructorDecl *(NominalTypeDecl *), Cached, NoLocationInfo)
179182
SWIFT_REQUEST(TypeChecker, HasDefaultInitRequest,

lib/AST/Decl.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3956,6 +3956,48 @@ ConstructorDecl *NominalTypeDecl::getDefaultInitializer() const {
39563956
SynthesizeDefaultInitRequest{mutableThis}, nullptr);
39573957
}
39583958

3959+
void NominalTypeDecl::synthesizeSemanticMembersIfNeeded(DeclName member) {
3960+
// Silently break cycles here because we can't be sure when and where a
3961+
// request to synthesize will come from yet.
3962+
// FIXME: rdar://56844567
3963+
if (Bits.NominalTypeDecl.IsComputingSemanticMembers)
3964+
return;
3965+
3966+
Bits.NominalTypeDecl.IsComputingSemanticMembers = true;
3967+
SWIFT_DEFER { Bits.NominalTypeDecl.IsComputingSemanticMembers = false; };
3968+
3969+
auto baseName = member.getBaseName();
3970+
auto &Context = getASTContext();
3971+
Optional<ImplicitMemberAction> action = None;
3972+
if (baseName == DeclBaseName::createConstructor())
3973+
action.emplace(ImplicitMemberAction::ResolveImplicitInit);
3974+
3975+
if (member.isSimpleName() && !baseName.isSpecial()) {
3976+
if (baseName.getIdentifier() == getASTContext().Id_CodingKeys) {
3977+
action.emplace(ImplicitMemberAction::ResolveCodingKeys);
3978+
}
3979+
} else {
3980+
auto argumentNames = member.getArgumentNames();
3981+
if (member.isCompoundName() && argumentNames.size() != 1)
3982+
return;
3983+
3984+
if (baseName == DeclBaseName::createConstructor() &&
3985+
(member.isSimpleName() || argumentNames.front() == Context.Id_from)) {
3986+
action.emplace(ImplicitMemberAction::ResolveDecodable);
3987+
} else if (!baseName.isSpecial() &&
3988+
baseName.getIdentifier() == Context.Id_encode &&
3989+
(member.isSimpleName() ||
3990+
argumentNames.front() == Context.Id_to)) {
3991+
action.emplace(ImplicitMemberAction::ResolveEncodable);
3992+
}
3993+
}
3994+
3995+
if (auto actionToTake = action) {
3996+
(void)evaluateOrDefault(Context.evaluator,
3997+
ResolveImplicitMemberRequest{this, actionToTake.getValue()}, false);
3998+
}
3999+
}
4000+
39594001
ClassDecl::ClassDecl(SourceLoc ClassLoc, Identifier Name, SourceLoc NameLoc,
39604002
MutableArrayRef<TypeLoc> Inherited,
39614003
GenericParamList *GenericParams, DeclContext *Parent)

lib/AST/NameLookup.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,34 @@ bool DeclContext::lookupQualified(Type type,
15171517
return lookupQualified(nominalTypesToLookInto, member, options, decls);
15181518
}
15191519

1520+
static void installPropertyWrapperMembersIfNeeded(NominalTypeDecl *target,
1521+
DeclName member) {
1522+
auto &Context = target->getASTContext();
1523+
auto baseName = member.getBaseName();
1524+
if (!member.isSimpleName() || baseName.isSpecial())
1525+
return;
1526+
1527+
if ((!baseName.getIdentifier().str().startswith("$") &&
1528+
!baseName.getIdentifier().str().startswith("_")) ||
1529+
baseName.getIdentifier().str().size() <= 1) {
1530+
return;
1531+
}
1532+
1533+
// $- and _-prefixed variables can be generated by properties that have
1534+
// attached property wrappers.
1535+
auto originalPropertyName =
1536+
Context.getIdentifier(baseName.getIdentifier().str().substr(1));
1537+
for (auto member : target->lookupDirect(originalPropertyName)) {
1538+
if (auto var = dyn_cast<VarDecl>(member)) {
1539+
if (var->hasAttachedPropertyWrapper()) {
1540+
auto sourceFile = var->getDeclContext()->getParentSourceFile();
1541+
if (sourceFile && sourceFile->Kind != SourceFileKind::Interface)
1542+
(void)var->getPropertyWrapperBackingProperty();
1543+
}
1544+
}
1545+
}
1546+
}
1547+
15201548
bool DeclContext::lookupQualified(ArrayRef<NominalTypeDecl *> typeDecls,
15211549
DeclName member,
15221550
NLOptions options,
@@ -1572,7 +1600,8 @@ bool DeclContext::lookupQualified(ArrayRef<NominalTypeDecl *> typeDecls,
15721600

15731601
// Make sure we've resolved implicit members, if we need them.
15741602
if (typeResolver) {
1575-
typeResolver->resolveImplicitMember(current, member);
1603+
current->synthesizeSemanticMembersIfNeeded(member);
1604+
installPropertyWrapperMembersIfNeeded(current, member);
15761605
}
15771606

15781607
// Look for results within the current nominal type and its extensions.

lib/AST/TypeCheckRequests.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,3 +1071,26 @@ void InheritsSuperclassInitializersRequest::cacheResult(bool value) const {
10711071
auto *decl = std::get<0>(getStorage());
10721072
decl->setInheritsSuperclassInitializers(value);
10731073
}
1074+
1075+
//----------------------------------------------------------------------------//
1076+
// ResolveImplicitMemberRequest computation.
1077+
//----------------------------------------------------------------------------//
1078+
1079+
void swift::simple_display(llvm::raw_ostream &out,
1080+
ImplicitMemberAction action) {
1081+
switch (action) {
1082+
case ImplicitMemberAction::ResolveImplicitInit:
1083+
out << "resolve implicit initializer";
1084+
break;
1085+
case ImplicitMemberAction::ResolveCodingKeys:
1086+
out << "resolve CodingKeys";
1087+
break;
1088+
case ImplicitMemberAction::ResolveEncodable:
1089+
out << "resolve Encodable.encode(to:)";
1090+
break;
1091+
case ImplicitMemberAction::ResolveDecodable:
1092+
out << "resolve Decodable.init(from:)";
1093+
break;
1094+
}
1095+
}
1096+

lib/Sema/CodeSynthesis.cpp

Lines changed: 50 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,12 +1075,12 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
10751075
(void)decl->getDefaultInitializer();
10761076
}
10771077

1078-
void TypeChecker::synthesizeMemberForLookup(NominalTypeDecl *target,
1079-
DeclName member) {
1080-
auto baseName = member.getBaseName();
1081-
1082-
if (baseName == DeclBaseName::createConstructor())
1083-
addImplicitConstructors(target);
1078+
llvm::Expected<bool>
1079+
ResolveImplicitMemberRequest::evaluate(Evaluator &evaluator,
1080+
NominalTypeDecl *target,
1081+
ImplicitMemberAction action) const {
1082+
// FIXME: This entire request is a layering violation made of smaller,
1083+
// finickier layering violations. See rdar://56844567
10841084

10851085
// Checks whether the target conforms to the given protocol. If the
10861086
// conformance is incomplete, force the conformance.
@@ -1091,9 +1091,9 @@ void TypeChecker::synthesizeMemberForLookup(NominalTypeDecl *target,
10911091
return false;
10921092

10931093
auto targetType = target->getDeclaredInterfaceType();
1094-
auto ref =
1095-
conformsToProtocol(targetType, protocol, target,
1096-
ConformanceCheckFlags::SkipConditionalRequirements);
1094+
auto ref = TypeChecker::conformsToProtocol(
1095+
targetType, protocol, target,
1096+
ConformanceCheckFlags::SkipConditionalRequirements);
10971097

10981098
if (ref.isInvalid()) {
10991099
return false;
@@ -1109,67 +1109,50 @@ void TypeChecker::synthesizeMemberForLookup(NominalTypeDecl *target,
11091109
return true;
11101110
};
11111111

1112-
if (member.isSimpleName() && !baseName.isSpecial()) {
1113-
if (baseName.getIdentifier() == Context.Id_CodingKeys) {
1114-
// CodingKeys is a special type which may be synthesized as part of
1115-
// Encodable/Decodable conformance. If the target conforms to either
1116-
// protocol and would derive conformance to either, the type may be
1117-
// synthesized.
1118-
// If the target conforms to either and the conformance has not yet been
1119-
// evaluated, then we should do that here.
1120-
//
1121-
// Try to synthesize Decodable first. If that fails, try to synthesize
1122-
// Encodable. If either succeeds and CodingKeys should have been
1123-
// synthesized, it will be synthesized.
1124-
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
1125-
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
1126-
if (!evaluateTargetConformanceTo(decodableProto))
1127-
(void)evaluateTargetConformanceTo(encodableProto);
1128-
}
1129-
1130-
if ((baseName.getIdentifier().str().startswith("$") ||
1131-
baseName.getIdentifier().str().startswith("_")) &&
1132-
baseName.getIdentifier().str().size() > 1) {
1133-
// $- and _-prefixed variables can be generated by properties that have
1134-
// attached property wrappers.
1135-
auto originalPropertyName =
1136-
Context.getIdentifier(baseName.getIdentifier().str().substr(1));
1137-
for (auto member : target->lookupDirect(originalPropertyName)) {
1138-
if (auto var = dyn_cast<VarDecl>(member)) {
1139-
if (var->hasAttachedPropertyWrapper()) {
1140-
auto sourceFile = var->getDeclContext()->getParentSourceFile();
1141-
if (sourceFile && sourceFile->Kind != SourceFileKind::Interface)
1142-
(void)var->getPropertyWrapperBackingPropertyInfo();
1143-
}
1144-
}
1145-
}
1146-
}
1147-
1148-
} else {
1149-
auto argumentNames = member.getArgumentNames();
1150-
if (member.isCompoundName() && argumentNames.size() != 1)
1151-
return;
1152-
1153-
if (baseName == DeclBaseName::createConstructor() &&
1154-
(member.isSimpleName() || argumentNames.front() == Context.Id_from)) {
1155-
// init(from:) may be synthesized as part of derived conformance to the
1156-
// Decodable protocol.
1157-
// If the target should conform to the Decodable protocol, check the
1158-
// conformance here to attempt synthesis.
1159-
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
1160-
(void)evaluateTargetConformanceTo(decodableProto);
1161-
} else if (!baseName.isSpecial() &&
1162-
baseName.getIdentifier() == Context.Id_encode &&
1163-
(member.isSimpleName() ||
1164-
argumentNames.front() == Context.Id_to)) {
1165-
// encode(to:) may be synthesized as part of derived conformance to the
1166-
// Encodable protocol.
1167-
// If the target should conform to the Encodable protocol, check the
1168-
// conformance here to attempt synthesis.
1169-
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
1112+
auto &Context = target->getASTContext();
1113+
switch (action) {
1114+
case ImplicitMemberAction::ResolveImplicitInit:
1115+
TypeChecker::addImplicitConstructors(target);
1116+
break;
1117+
case ImplicitMemberAction::ResolveCodingKeys: {
1118+
// CodingKeys is a special type which may be synthesized as part of
1119+
// Encodable/Decodable conformance. If the target conforms to either
1120+
// protocol and would derive conformance to either, the type may be
1121+
// synthesized.
1122+
// If the target conforms to either and the conformance has not yet been
1123+
// evaluated, then we should do that here.
1124+
//
1125+
// Try to synthesize Decodable first. If that fails, try to synthesize
1126+
// Encodable. If either succeeds and CodingKeys should have been
1127+
// synthesized, it will be synthesized.
1128+
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
1129+
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
1130+
if (!evaluateTargetConformanceTo(decodableProto)) {
11701131
(void)evaluateTargetConformanceTo(encodableProto);
11711132
}
11721133
}
1134+
break;
1135+
case ImplicitMemberAction::ResolveEncodable: {
1136+
// encode(to:) may be synthesized as part of derived conformance to the
1137+
// Encodable protocol.
1138+
// If the target should conform to the Encodable protocol, check the
1139+
// conformance here to attempt synthesis.
1140+
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
1141+
(void)evaluateTargetConformanceTo(encodableProto);
1142+
}
1143+
break;
1144+
case ImplicitMemberAction::ResolveDecodable: {
1145+
// init(from:) may be synthesized as part of derived conformance to the
1146+
// Decodable protocol.
1147+
// If the target should conform to the Decodable protocol, check the
1148+
// conformance here to attempt synthesis.
1149+
TypeChecker::addImplicitConstructors(target);
1150+
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
1151+
(void)evaluateTargetConformanceTo(decodableProto);
1152+
}
1153+
break;
1154+
}
1155+
return true;
11731156
}
11741157

11751158
llvm::Expected<bool>

0 commit comments

Comments
 (0)