Skip to content

Commit e864285

Browse files
author
Itai Ferber
committed
Expose synthesized members in AST lookups
Some types and members are synthesized by derived protocol conformances (e.g. the CodingKeys member type or init(from:)/encode(to:) members from Decodable/Encodable conformance) — however, they are not visible in AST lookup if they have not been synthesized. Exposes a LazyResolver callback for performing member synthesis where relevant during qualified lookups to synthesize these members on demand when needed.
1 parent 8793212 commit e864285

9 files changed

+1432
-3
lines changed

include/swift/AST/LazyResolver.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ class LazyResolver {
9191
/// Resolve any implicitly-declared constructors within the given nominal.
9292
virtual void resolveImplicitConstructors(NominalTypeDecl *nominal) = 0;
9393

94+
/// Resolve an implicitly-generated member with the given name.
95+
virtual void resolveImplicitMember(NominalTypeDecl *nominal, DeclName member) = 0;
96+
9497
/// Resolve any implicitly-generated members and conformances for generated
9598
/// external decls.
9699
virtual void resolveExternalDeclImplicitMembers(NominalTypeDecl *nominal) = 0;
@@ -156,6 +159,10 @@ class DelegatingLazyResolver : public LazyResolver {
156159
Principal.resolveImplicitConstructors(nominal);
157160
}
158161

162+
void resolveImplicitMember(NominalTypeDecl *nominal, DeclName member) override {
163+
Principal.resolveImplicitMember(nominal, member);
164+
}
165+
159166
void resolveExternalDeclImplicitMembers(NominalTypeDecl *nominal) override {
160167
Principal.resolveExternalDeclImplicitMembers(nominal);
161168
}

lib/AST/NameLookup.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,9 +1499,13 @@ bool DeclContext::lookupQualified(Type type,
14991499
if (tracker)
15001500
tracker->addUsedMember({current, member.getBaseName()},isLookupCascading);
15011501

1502-
// Make sure we've resolved implicit constructors, if we need them.
1503-
if (member.getBaseName() == ctx.Id_init && typeResolver)
1504-
typeResolver->resolveImplicitConstructors(current);
1502+
// Make sure we've resolved implicit members, if we need them.
1503+
if (typeResolver) {
1504+
if (member.getBaseName() == ctx.Id_init)
1505+
typeResolver->resolveImplicitConstructors(current);
1506+
1507+
typeResolver->resolveImplicitMember(current, member);
1508+
}
15051509

15061510
// Look for results within the current nominal type and its extensions.
15071511
bool currentIsProtocol = isa<ProtocolDecl>(current);

lib/Sema/DerivedConformanceCodingKey.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ deriveBodyCodingKey_init_stringValue(AbstractFunctionDecl *initDecl) {
393393
/// \param enumDecl The enum to check.
394394
static bool canSynthesizeCodingKey(TypeChecker &tc, Decl *parentDecl,
395395
EnumDecl *enumDecl) {
396+
// Validate the enum and its raw type.
397+
tc.validateDecl(enumDecl);
398+
396399
// If the enum has a raw type (optional), it must be String or Int.
397400
Type rawType = enumDecl->getRawType();
398401
if (rawType) {

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ static ConstructorDecl *deriveRawRepresentable_init(TypeChecker &tc,
334334

335335
static bool canSynthesizeRawRepresentable(TypeChecker &tc, Decl *parentDecl,
336336
EnumDecl *enumDecl) {
337+
// Validate the enum and its raw type.
338+
tc.validateDecl(enumDecl);
337339

338340
// It must have a valid raw type.
339341
Type rawType = enumDecl->getRawType();

lib/Sema/TypeCheckDecl.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// A similar struct with Codable properties adopting Codable should get a
2+
// synthesized init(from:), along with the memberwise initializer.
13
//===--- TypeCheckDecl.cpp - Type Checking for Declarations ---------------===//
24
//
35
// This source file is part of the Swift.org open source project
@@ -8324,6 +8326,96 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
83248326
}
83258327
}
83268328

8329+
void TypeChecker::synthesizeMemberForLookup(NominalTypeDecl *target,
8330+
DeclName member) {
8331+
auto baseName = member.getBaseName();
8332+
if (baseName.isSpecial())
8333+
return;
8334+
8335+
// Checks whether the target conforms to the given protocol. If the
8336+
// conformance is incomplete, check the conformance to force synthesis, if
8337+
// possible.
8338+
//
8339+
// Swallows diagnostics if conformance checking is already in progress (so we
8340+
// don't display diagnostics twice).
8341+
//
8342+
// Returns whether the target conforms to the protocol and the conformance is
8343+
// complete.
8344+
auto evaluateTargetConformanceTo = [&](ProtocolDecl *protocol) {
8345+
auto targetType = target->getDeclaredInterfaceType();
8346+
if (auto ref = conformsToProtocol(targetType, protocol, target,
8347+
ConformanceCheckFlags::Used,
8348+
SourceLoc())) {
8349+
if (auto *conformance =
8350+
dyn_cast_or_null<NormalProtocolConformance>(ref->getConcrete())) {
8351+
if (conformance->isIncomplete()) {
8352+
// Check conformance, forcing synthesis.
8353+
//
8354+
// If synthesizing conformance fails, this will produce diagnostics.
8355+
// If conformance checking was already in progress elsewhere, though,
8356+
// this could produce diagnostics twice.
8357+
//
8358+
// To prevent this duplication, we swallow the diagnostics if the
8359+
// state of the conformance is not Incomplete.
8360+
DiagnosticTransaction transaction(Context.Diags);
8361+
auto shouldSwallowDiagnostics =
8362+
conformance->getState() != ProtocolConformanceState::Incomplete;
8363+
8364+
checkConformance(conformance);
8365+
if (shouldSwallowDiagnostics)
8366+
transaction.abort();
8367+
8368+
return conformance->isComplete();
8369+
}
8370+
}
8371+
}
8372+
8373+
return false;
8374+
};
8375+
8376+
if (member.isSimpleName()) {
8377+
if (baseName.getIdentifier() == Context.Id_CodingKeys) {
8378+
// CodingKeys is a special type which may be synthesized as part of
8379+
// Encodable/Decodable conformance. If the target conforms to either
8380+
// protocol and would derive conformance to either, the type may be
8381+
// synthesized.
8382+
// If the target conforms to either and the conformance has not yet been
8383+
// evaluated, then we should do that here.
8384+
//
8385+
// Try to synthesize Decodable first. If that fails, try to synthesize
8386+
// Encodable. If either succeeds and CodingKeys should have been
8387+
// synthesized, it will be synthesized.
8388+
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
8389+
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
8390+
if (!evaluateTargetConformanceTo(decodableProto))
8391+
(void)evaluateTargetConformanceTo(encodableProto);
8392+
}
8393+
} else {
8394+
auto argumentNames = member.getArgumentNames();
8395+
if (argumentNames.size() != 1)
8396+
return;
8397+
8398+
auto argumentName = argumentNames.front();
8399+
if (baseName.getIdentifier() == Context.Id_init &&
8400+
argumentName == Context.Id_from) {
8401+
// init(from:) may be synthesized as part of derived confromance to the
8402+
// Decodable protocol.
8403+
// If the target should conform to the Decodable protocol, check the
8404+
// conformance here to attempt synthesis.
8405+
auto *decodableProto = Context.getProtocol(KnownProtocolKind::Decodable);
8406+
(void)evaluateTargetConformanceTo(decodableProto);
8407+
} else if (baseName.getIdentifier() == Context.Id_encode &&
8408+
argumentName == Context.Id_to) {
8409+
// encode(to:) may be synthesized as part of derived confromance to the
8410+
// Encodable protocol.
8411+
// If the target should conform to the Encodable protocol, check the
8412+
// conformance here to attempt synthesis.
8413+
auto *encodableProto = Context.getProtocol(KnownProtocolKind::Encodable);
8414+
(void)evaluateTargetConformanceTo(encodableProto);
8415+
}
8416+
}
8417+
}
8418+
83278419
void TypeChecker::addImplicitStructConformances(StructDecl *SD) {
83288420
// Type-check the protocol conformances of the struct decl to instantiate its
83298421
// derived conformances.

lib/Sema/TypeChecker.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,10 @@ class TypeChecker final : public LazyResolver {
12641264
addImplicitConstructors(nominal);
12651265
}
12661266

1267+
virtual void resolveImplicitMember(NominalTypeDecl *nominal, DeclName member) override {
1268+
synthesizeMemberForLookup(nominal, member);
1269+
}
1270+
12671271
virtual void
12681272
resolveExternalDeclImplicitMembers(NominalTypeDecl *nominal) override {
12691273
handleExternalDecl(nominal);
@@ -1438,6 +1442,11 @@ class TypeChecker final : public LazyResolver {
14381442
/// enum with a raw type.
14391443
void addImplicitEnumConformances(EnumDecl *ED);
14401444

1445+
/// Synthesize the member with the given name on the target if applicable,
1446+
/// i.e. if the member is synthesizable and has not yet been added to the
1447+
/// target.
1448+
void synthesizeMemberForLookup(NominalTypeDecl *target, DeclName member);
1449+
14411450
/// The specified AbstractStorageDecl \c storage was just found to satisfy
14421451
/// the protocol property \c requirement. Ensure that it has the full
14431452
/// complement of accessors.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown
2+
3+
class SynthesizedSuperclass : Codable {
4+
let superValue: Double = .pi
5+
}
6+
7+
// Classes which subclass something Codable should be able to override their
8+
// superclasses methods (the methods should be visible in member lookup despite
9+
// being synthesized).
10+
class ExplicitSubclass : SynthesizedSuperclass {
11+
required init(from decoder: Decoder) throws {
12+
try super.init(from: decoder)
13+
}
14+
15+
override func encode(to encoder: Encoder) throws {
16+
try super.encode(to: encoder)
17+
}
18+
}

0 commit comments

Comments
 (0)