Skip to content

Commit 51aebd2

Browse files
committed
AST: Fix name lookup from within lazy property initializers
Allow instance properties and methods to be referenced from within a lazy property initializer, with or without explicit 'self.' qualification. The old behavior in Swift 3 was an incredible combination of odd quirks: - If the lazy property had an explicitly-written type, it was possible to reference instance members from the initializer expression by explicitly prefixing 'self.'. - However, if the lazy property type is inferred, it would first be type checked in the initializer context, which has no 'self' available. - Unqualified references to instance members did not work at all, because name lookup thought the "location" of the lookup was outside of the body of the getter. - Unqualified references to static properties worked, however unqualified references to static methods did not, and produced a bogus diagnostic, because one part of the name lookup code thought that initializers were "instance context" and another thought they were "static context". This patch improves on the old behavior with the following fixes: - Give PatternBindingInitializers associated with lazy properties an implicit 'self' declaration for use by name lookup. - In order to allow "re-parenting" the initializer after it has been type checked into the body of the getter, "steal" the initializer's 'self' when buiding the getter. - Fix up name lookup and make it aware of the implicit 'self' decl of a PatternBindingInitializer. This improves upon an earlier fix for this issue by Doug Gregor which only worked with ASTScope enabled; the new fix is more general and shares logic between the two name lookup implementations. Fixes <rdar://problem/16888679>, <https://bugs.swift.org/browse/SR-48>, <https://bugs.swift.org/browse/SR-2203>, <https://bugs.swift.org/browse/SR-4663>, and the countless other dupes of this issue.
1 parent 4b7589d commit 51aebd2

File tree

8 files changed

+145
-66
lines changed

8 files changed

+145
-66
lines changed

include/swift/AST/Initializer.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,21 @@ class Initializer : public DeclContext {
7070
class PatternBindingInitializer : public Initializer {
7171
PatternBindingDecl *Binding;
7272

73+
// created lazily for 'self' lookup from lazy property initializer
74+
ParamDecl *SelfParam;
75+
7376
friend class ASTContext; // calls reset on unused contexts
7477

7578
void reset(DeclContext *parent) {
7679
setParent(parent);
7780
Binding = nullptr;
81+
SelfParam = nullptr;
7882
}
7983

8084
public:
8185
explicit PatternBindingInitializer(DeclContext *parent)
8286
: Initializer(InitializerKind::PatternBinding, parent),
83-
Binding(nullptr) {
87+
Binding(nullptr), SelfParam(nullptr) {
8488
SpareBits = 0;
8589
}
8690

@@ -95,6 +99,8 @@ class PatternBindingInitializer : public Initializer {
9599

96100
unsigned getBindingIndex() const { return SpareBits; }
97101

102+
ParamDecl *getImplicitSelfDecl();
103+
98104
static bool classof(const DeclContext *DC) {
99105
if (auto init = dyn_cast<Initializer>(DC))
100106
return classof(init);

lib/AST/ASTScope.cpp

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,28 +1903,17 @@ SmallVector<ValueDecl *, 4> ASTScope::getLocalBindings() const {
19031903
}
19041904
break;
19051905

1906-
case ASTScopeKind::PatternInitializer:
1907-
// FIXME: This causes recursion that we cannot yet handle.
1908-
#if false
1906+
case ASTScopeKind::PatternInitializer: {
19091907
// 'self' is available within the pattern initializer of a 'lazy' variable.
1910-
if (auto singleVar = patternBinding.decl->getSingleVar()) {
1911-
if (singleVar->getAttrs().hasAttribute<LazyAttr>() &&
1912-
singleVar->getDeclContext()->isTypeContext()) {
1913-
// If there is no getter (yet), add them.
1914-
if (!singleVar->getGetter()) {
1915-
ASTContext &ctx = singleVar->getASTContext();
1916-
if (auto resolver = ctx.getLazyResolver())
1917-
resolver->introduceLazyVarAccessors(singleVar);
1918-
}
1919-
1920-
// Add the getter's 'self'.
1921-
if (auto getter = singleVar->getGetter())
1922-
if (auto self = getter->getImplicitSelfDecl())
1923-
result.push_back(self);
1924-
}
1908+
auto *initContext = cast_or_null<PatternBindingInitializer>(
1909+
patternBinding.decl->getPatternList()[0].getInitContext());
1910+
if (initContext) {
1911+
if (auto *selfParam = initContext->getImplicitSelfDecl())
1912+
result.push_back(selfParam);
19251913
}
1926-
#endif
1914+
19271915
break;
1916+
}
19281917

19291918
case ASTScopeKind::Closure:
19301919
// Note: Parameters all at once is different from functions, but it's not

lib/AST/Decl.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,25 @@ PatternBindingDecl *PatternBindingDecl::createDeserialized(
986986
return PBD;
987987
}
988988

989+
ParamDecl *PatternBindingInitializer::getImplicitSelfDecl() {
990+
if (SelfParam)
991+
return SelfParam;
992+
993+
if (auto singleVar = getBinding()->getSingleVar()) {
994+
auto *DC = singleVar->getDeclContext();
995+
if (singleVar->getAttrs().hasAttribute<LazyAttr>() &&
996+
DC->isTypeContext()) {
997+
bool isInOut = !DC->getDeclaredTypeOfContext()->hasReferenceSemantics();
998+
SelfParam = ParamDecl::createSelf(SourceLoc(), DC,
999+
singleVar->isStatic(),
1000+
isInOut);
1001+
SelfParam->setDeclContext(this);
1002+
}
1003+
}
1004+
1005+
return SelfParam;
1006+
}
1007+
9891008
static bool patternContainsVarDeclBinding(const Pattern *P, const VarDecl *VD) {
9901009
bool Result = false;
9911010
P->forEachVariable([&](VarDecl *FoundVD) {
@@ -3916,9 +3935,12 @@ Pattern *VarDecl::getParentPattern() const {
39163935
}
39173936

39183937
bool VarDecl::isSelfParameter() const {
3919-
if (isa<ParamDecl>(this))
3938+
if (isa<ParamDecl>(this)) {
39203939
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(getDeclContext()))
39213940
return AFD->getImplicitSelfDecl() == this;
3941+
if (auto *PBI = dyn_cast<PatternBindingInitializer>(getDeclContext()))
3942+
return PBI->getImplicitSelfDecl() == this;
3943+
}
39223944

39233945
return false;
39243946
}

lib/AST/NameLookup.cpp

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -510,29 +510,11 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
510510
// Pattern binding initializers are only interesting insofar as they
511511
// affect lookup in an enclosing nominal type or extension thereof.
512512
if (auto *bindingInit = dyn_cast<PatternBindingInitializer>(dc)) {
513-
if (auto binding = bindingInit->getBinding()) {
514-
// Look for 'self' for a lazy variable initializer.
515-
if (auto singleVar = binding->getSingleVar())
516-
// We only care about lazy variables.
517-
if (singleVar->getAttrs().hasAttribute<LazyAttr>()) {
518-
519-
// 'self' will be listed in the local bindings.
520-
for (auto local : localBindings) {
521-
auto param = dyn_cast<ParamDecl>(local);
522-
if (!param) continue;
523-
524-
525-
// If we have a variable that's the implicit self of its enclosing
526-
// context, mark it as 'self'.
527-
if (auto func = dyn_cast<FuncDecl>(param->getDeclContext())) {
528-
if (param == func->getImplicitSelfDecl()) {
529-
selfDecl = param;
530-
break;
531-
}
532-
}
533-
}
534-
}
535-
}
513+
// Lazy variable initializer contexts have a 'self' parameter for
514+
// instance member lookup.
515+
if (auto *selfParam = bindingInit->getImplicitSelfDecl())
516+
selfDecl = selfParam;
517+
536518
continue;
537519
}
538520

@@ -646,17 +628,46 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
646628
Type ExtendedType;
647629
bool isTypeLookup = false;
648630

649-
// If this declcontext is an initializer for a static property, then we're
650-
// implicitly doing a static lookup into the parent declcontext.
651-
if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC))
652-
if (!DC->getParent()->isModuleScopeContext()) {
653-
if (auto *PBD = PBI->getBinding()) {
654-
isTypeLookup = PBD->isStatic();
655-
DC = DC->getParent();
656-
}
631+
if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC)) {
632+
auto *PBD = PBI->getBinding();
633+
assert(PBD);
634+
635+
// Lazy variable initializer contexts have a 'self' parameter for
636+
// instance member lookup.
637+
if (auto *selfParam = PBI->getImplicitSelfDecl()) {
638+
Consumer.foundDecl(selfParam,
639+
DeclVisibilityKind::FunctionParameter);
640+
if (!Results.empty())
641+
return;
642+
643+
DC = DC->getParent();
644+
645+
BaseDecl = selfParam;
646+
ExtendedType = DC->getSelfTypeInContext();
647+
MetaBaseDecl = DC->getAsNominalTypeOrNominalTypeExtensionContext();
648+
649+
isTypeLookup = PBD->isStatic();
650+
}
651+
// Initializers for stored properties of types perform static
652+
// lookup into the surrounding context.
653+
else if (PBD->getDeclContext()->isTypeContext()) {
654+
DC = DC->getParent();
655+
656+
ExtendedType = DC->getSelfTypeInContext();
657+
MetaBaseDecl = DC->getAsNominalTypeOrNominalTypeExtensionContext();
658+
BaseDecl = MetaBaseDecl;
659+
660+
isTypeLookup = PBD->isStatic(); // FIXME
661+
662+
isCascadingUse = DC->isCascadingContextForLookup(false);
657663
}
658-
659-
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(DC)) {
664+
// Otherwise, we have an initializer for a global or local property.
665+
// There's not much to find here, we'll keep going up to a parent
666+
// context.
667+
668+
if (!isCascadingUse.hasValue())
669+
isCascadingUse = DC->isCascadingContextForLookup(false);
670+
} else if (auto *AFD = dyn_cast<AbstractFunctionDecl>(DC)) {
660671
// Look for local variables; normally, the parser resolves these
661672
// for us, but it can't do the right thing inside local types.
662673
// FIXME: when we can parse and typecheck the function body partially
@@ -690,9 +701,12 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
690701
if (FD->isStatic())
691702
isTypeLookup = true;
692703

693-
// If we're not in the body of the function, the base declaration
704+
// If we're not in the body of the function (for example, we
705+
// might be type checking a default argument expression and
706+
// performing name lookup from there), the base declaration
694707
// is the nominal type, not 'self'.
695-
if (Loc.isValid() &&
708+
if (!AFD->isImplicit() &&
709+
Loc.isValid() &&
696710
AFD->getBodySourceRange().isValid() &&
697711
!SM.rangeContainsTokenLoc(AFD->getBodySourceRange(), Loc)) {
698712
BaseDecl = MetaBaseDecl;

lib/Sema/CodeSynthesis.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/Availability.h"
2323
#include "swift/AST/Expr.h"
2424
#include "swift/AST/GenericEnvironment.h"
25+
#include "swift/AST/Initializer.h"
2526
#include "swift/AST/ParameterList.h"
2627
#include "swift/AST/ProtocolConformance.h"
2728
#include "swift/Basic/Defer.h"
@@ -143,10 +144,25 @@ static FuncDecl *createGetterPrototype(AbstractStorageDecl *storage,
143144
SmallVector<ParameterList*, 2> getterParams;
144145

145146
// The implicit 'self' argument if in a type context.
146-
if (storage->getDeclContext()->isTypeContext())
147-
getterParams.push_back(ParameterList::createSelf(loc,
148-
storage->getDeclContext(),
149-
/*isStatic*/false));
147+
if (storage->getDeclContext()->isTypeContext()) {
148+
ParamDecl *selfDecl;
149+
150+
// For lazy properties, steal the 'self' from the initializer context.
151+
if (storage->getAttrs().hasAttribute<LazyAttr>()) {
152+
auto *varDecl = cast<VarDecl>(storage);
153+
auto *bindingDecl = varDecl->getParentPatternBinding();
154+
auto *bindingInit = cast<PatternBindingInitializer>(
155+
bindingDecl->getPatternEntryForVarDecl(varDecl).getInitContext());
156+
157+
selfDecl = bindingInit->getImplicitSelfDecl();
158+
} else {
159+
selfDecl = ParamDecl::createSelf(loc,
160+
storage->getDeclContext(),
161+
/*isStatic*/false);
162+
}
163+
164+
getterParams.push_back(ParameterList::create(TC.Context, selfDecl));
165+
}
150166

151167
// Add an index-forwarding clause.
152168
getterParams.push_back(buildIndexForwardingParamList(storage, {}));
@@ -1146,7 +1162,6 @@ static FuncDecl *completeLazyPropertyGetter(VarDecl *VD, VarDecl *Storage,
11461162
// realize that they are in the getter function.
11471163
InitValue->walk(RecontextualizeClosures(Get));
11481164

1149-
11501165
Pattern *Tmp2PBDPattern = new (Ctx) NamedPattern(Tmp2VD, /*implicit*/true);
11511166
Tmp2PBDPattern = new (Ctx) TypedPattern(Tmp2PBDPattern,
11521167
TypeLoc::withoutLoc(VD->getType()),

test/NameBinding/name_lookup.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ class r21677702 {
528528
// <rdar://problem/16954496> lazy properties must use "self." in their body, and can weirdly refer to class variables directly
529529
class r16954496 {
530530
func bar() {}
531-
lazy var x: Array<(r16954496) -> () -> Void> = [bar]
531+
lazy var x: Array<() -> Void> = [bar]
532532
}
533533

534534

test/NameBinding/scope_map.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,7 @@ class LazyProperties {
460460
// CHECK-SEARCHES-NEXT: ClassDecl name=LazyProperties
461461
// CHECK-SEARCHES-NEXT: Initializer PatternBinding {{.*}} #0
462462

463-
// FIXME: Re-enable the binding below
464-
// CHECK-SEARCHES-NOT: Local bindings: self
463+
// CHECK-SEARCHES-NEXT: Local bindings: self
465464

466465
// CHECK-SEARCHES-LABEL: ***Complete scope map***
467466
// CHECK-SEARCHES-NEXT: SourceFile {{.*}} '{{.*}}scope_map.swift' [1:1 - [[EOF:[0-9]+:[0-9]+]]] unexpanded

test/decl/var/lazy_properties.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// RUN: %target-typecheck-verify-swift -parse-as-library
1+
// RUN: %target-typecheck-verify-swift -parse-as-library -swift-version 3
2+
// RUN: %target-typecheck-verify-swift -parse-as-library -swift-version 4
23

34
lazy func lazy_func() {} // expected-error {{'lazy' may only be used on 'var' declarations}} {{1-6=}}
45

@@ -72,7 +73,8 @@ struct StructTest {
7273

7374
// <rdar://problem/16889110> capture lists in lazy member properties cannot use self
7475
class CaptureListInLazyProperty {
75-
lazy var closure: () -> Int = { [weak self] in return self!.i }
76+
lazy var closure1 = { [weak self] in return self!.i }
77+
lazy var closure2: () -> Int = { [weak self] in return self!.i }
7678
var i = 42
7779
}
7880

@@ -119,3 +121,35 @@ struct Construction {
119121
class Constructor {
120122
lazy var myQ = Construction(x: 3)
121123
}
124+
125+
126+
// Problems with self references
127+
class BaseClass {
128+
var baseInstanceProp = 42
129+
static var baseStaticProp = 42
130+
}
131+
132+
class ReferenceSelfInLazyProperty : BaseClass {
133+
lazy var refs = (i, f())
134+
lazy var trefs: (Int, Int) = (i, f())
135+
136+
lazy var qrefs = (self.i, self.f())
137+
lazy var qtrefs: (Int, Int) = (self.i, self.f())
138+
139+
lazy var crefs = { (i, f()) }()
140+
lazy var ctrefs: (Int, Int) = { (i, f()) }()
141+
142+
lazy var cqrefs = { (self.i, self.f()) }()
143+
lazy var cqtrefs: (Int, Int) = { (self.i, self.f()) }()
144+
145+
lazy var mrefs = { () -> (Int, Int) in return (i, f()) }()
146+
// expected-error@-1 {{call to method 'f' in closure requires explicit 'self.' to make capture semantics explicit}}
147+
// expected-error@-2 {{reference to property 'i' in closure requires explicit 'self.' to make capture semantics explicit}}
148+
lazy var mtrefs: (Int, Int) = { return (i, f()) }()
149+
150+
lazy var mqrefs = { () -> (Int, Int) in (self.i, self.f()) }()
151+
lazy var mqtrefs: (Int, Int) = { return (self.i, self.f()) }()
152+
153+
var i = 42
154+
func f() -> Int { return 0 }
155+
}

0 commit comments

Comments
 (0)