Skip to content

Allow lazy property initializers to reference instance members without explicit 'self.' #9920

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 2 commits into from
May 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion include/swift/AST/Initializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,21 @@ class Initializer : public DeclContext {
class PatternBindingInitializer : public Initializer {
PatternBindingDecl *Binding;

// created lazily for 'self' lookup from lazy property initializer
ParamDecl *SelfParam;

friend class ASTContext; // calls reset on unused contexts

void reset(DeclContext *parent) {
setParent(parent);
Binding = nullptr;
SelfParam = nullptr;
}

public:
explicit PatternBindingInitializer(DeclContext *parent)
: Initializer(InitializerKind::PatternBinding, parent),
Binding(nullptr) {
Binding(nullptr), SelfParam(nullptr) {
SpareBits = 0;
}

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

unsigned getBindingIndex() const { return SpareBits; }

ParamDecl *getImplicitSelfDecl();

static bool classof(const DeclContext *DC) {
if (auto init = dyn_cast<Initializer>(DC))
return classof(init);
Expand Down
7 changes: 0 additions & 7 deletions include/swift/AST/LazyResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ class LazyResolver {
/// Bind an extension to its extended type.
virtual void bindExtension(ExtensionDecl *ext) = 0;

/// Introduce the accessors for a 'lazy' variable.
virtual void introduceLazyVarAccessors(VarDecl *var) = 0;

/// Resolve the type of an extension.
///
/// This can be called to ensure that the members of an extension can be
Expand Down Expand Up @@ -151,10 +148,6 @@ class DelegatingLazyResolver : public LazyResolver {
Principal.bindExtension(ext);
}

void introduceLazyVarAccessors(VarDecl *var) override {
Principal.introduceLazyVarAccessors(var);
}

void resolveExtension(ExtensionDecl *ext) override {
Principal.resolveExtension(ext);
}
Expand Down
27 changes: 8 additions & 19 deletions lib/AST/ASTScope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1903,28 +1903,17 @@ SmallVector<ValueDecl *, 4> ASTScope::getLocalBindings() const {
}
break;

case ASTScopeKind::PatternInitializer:
// FIXME: This causes recursion that we cannot yet handle.
#if false
case ASTScopeKind::PatternInitializer: {
// 'self' is available within the pattern initializer of a 'lazy' variable.
if (auto singleVar = patternBinding.decl->getSingleVar()) {
if (singleVar->getAttrs().hasAttribute<LazyAttr>() &&
singleVar->getDeclContext()->isTypeContext()) {
// If there is no getter (yet), add them.
if (!singleVar->getGetter()) {
ASTContext &ctx = singleVar->getASTContext();
if (auto resolver = ctx.getLazyResolver())
resolver->introduceLazyVarAccessors(singleVar);
}

// Add the getter's 'self'.
if (auto getter = singleVar->getGetter())
if (auto self = getter->getImplicitSelfDecl())
result.push_back(self);
}
auto *initContext = cast_or_null<PatternBindingInitializer>(
patternBinding.decl->getPatternList()[0].getInitContext());
if (initContext) {
if (auto *selfParam = initContext->getImplicitSelfDecl())
result.push_back(selfParam);
}
#endif

break;
}

case ASTScopeKind::Closure:
// Note: Parameters all at once is different from functions, but it's not
Expand Down
24 changes: 23 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,25 @@ PatternBindingDecl *PatternBindingDecl::createDeserialized(
return PBD;
}

ParamDecl *PatternBindingInitializer::getImplicitSelfDecl() {
if (SelfParam)
return SelfParam;

if (auto singleVar = getBinding()->getSingleVar()) {
auto *DC = singleVar->getDeclContext();
if (singleVar->getAttrs().hasAttribute<LazyAttr>() &&
DC->isTypeContext()) {
bool isInOut = !DC->getDeclaredTypeOfContext()->hasReferenceSemantics();
SelfParam = ParamDecl::createSelf(SourceLoc(), DC,
singleVar->isStatic(),
isInOut);
SelfParam->setDeclContext(this);
}
}

return SelfParam;
}

static bool patternContainsVarDeclBinding(const Pattern *P, const VarDecl *VD) {
bool Result = false;
P->forEachVariable([&](VarDecl *FoundVD) {
Expand Down Expand Up @@ -3916,9 +3935,12 @@ Pattern *VarDecl::getParentPattern() const {
}

bool VarDecl::isSelfParameter() const {
if (isa<ParamDecl>(this))
if (isa<ParamDecl>(this)) {
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(getDeclContext()))
return AFD->getImplicitSelfDecl() == this;
if (auto *PBI = dyn_cast<PatternBindingInitializer>(getDeclContext()))
return PBI->getImplicitSelfDecl() == this;
}

return false;
}
Expand Down
84 changes: 49 additions & 35 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,29 +510,11 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
// Pattern binding initializers are only interesting insofar as they
// affect lookup in an enclosing nominal type or extension thereof.
if (auto *bindingInit = dyn_cast<PatternBindingInitializer>(dc)) {
if (auto binding = bindingInit->getBinding()) {
// Look for 'self' for a lazy variable initializer.
if (auto singleVar = binding->getSingleVar())
// We only care about lazy variables.
if (singleVar->getAttrs().hasAttribute<LazyAttr>()) {

// 'self' will be listed in the local bindings.
for (auto local : localBindings) {
auto param = dyn_cast<ParamDecl>(local);
if (!param) continue;


// If we have a variable that's the implicit self of its enclosing
// context, mark it as 'self'.
if (auto func = dyn_cast<FuncDecl>(param->getDeclContext())) {
if (param == func->getImplicitSelfDecl()) {
selfDecl = param;
break;
}
}
}
}
}
// Lazy variable initializer contexts have a 'self' parameter for
// instance member lookup.
if (auto *selfParam = bindingInit->getImplicitSelfDecl())
selfDecl = selfParam;

continue;
}

Expand Down Expand Up @@ -646,17 +628,46 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
Type ExtendedType;
bool isTypeLookup = false;

// If this declcontext is an initializer for a static property, then we're
// implicitly doing a static lookup into the parent declcontext.
if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC))
if (!DC->getParent()->isModuleScopeContext()) {
if (auto *PBD = PBI->getBinding()) {
isTypeLookup = PBD->isStatic();
DC = DC->getParent();
}
if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC)) {
auto *PBD = PBI->getBinding();
assert(PBD);

// Lazy variable initializer contexts have a 'self' parameter for
// instance member lookup.
if (auto *selfParam = PBI->getImplicitSelfDecl()) {
Consumer.foundDecl(selfParam,
DeclVisibilityKind::FunctionParameter);
if (!Results.empty())
return;

DC = DC->getParent();

BaseDecl = selfParam;
ExtendedType = DC->getSelfTypeInContext();
MetaBaseDecl = DC->getAsNominalTypeOrNominalTypeExtensionContext();

isTypeLookup = PBD->isStatic();
}
// Initializers for stored properties of types perform static
// lookup into the surrounding context.
else if (PBD->getDeclContext()->isTypeContext()) {
DC = DC->getParent();

ExtendedType = DC->getSelfTypeInContext();
MetaBaseDecl = DC->getAsNominalTypeOrNominalTypeExtensionContext();
BaseDecl = MetaBaseDecl;

isTypeLookup = PBD->isStatic(); // FIXME

isCascadingUse = DC->isCascadingContextForLookup(false);
}

if (auto *AFD = dyn_cast<AbstractFunctionDecl>(DC)) {
// Otherwise, we have an initializer for a global or local property.
// There's not much to find here, we'll keep going up to a parent
// context.

if (!isCascadingUse.hasValue())
isCascadingUse = DC->isCascadingContextForLookup(false);
} else if (auto *AFD = dyn_cast<AbstractFunctionDecl>(DC)) {
// Look for local variables; normally, the parser resolves these
// for us, but it can't do the right thing inside local types.
// FIXME: when we can parse and typecheck the function body partially
Expand Down Expand Up @@ -690,9 +701,12 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
if (FD->isStatic())
isTypeLookup = true;

// If we're not in the body of the function, the base declaration
// If we're not in the body of the function (for example, we
// might be type checking a default argument expression and
// performing name lookup from there), the base declaration
// is the nominal type, not 'self'.
if (Loc.isValid() &&
if (!AFD->isImplicit() &&
Loc.isValid() &&
AFD->getBodySourceRange().isValid() &&
!SM.rangeContainsTokenLoc(AFD->getBodySourceRange(), Loc)) {
BaseDecl = MetaBaseDecl;
Expand Down
29 changes: 20 additions & 9 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "swift/AST/Availability.h"
#include "swift/AST/Expr.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/Basic/Defer.h"
Expand Down Expand Up @@ -143,10 +144,25 @@ static FuncDecl *createGetterPrototype(AbstractStorageDecl *storage,
SmallVector<ParameterList*, 2> getterParams;

// The implicit 'self' argument if in a type context.
if (storage->getDeclContext()->isTypeContext())
getterParams.push_back(ParameterList::createSelf(loc,
storage->getDeclContext(),
/*isStatic*/false));
if (storage->getDeclContext()->isTypeContext()) {
ParamDecl *selfDecl;

// For lazy properties, steal the 'self' from the initializer context.
if (storage->getAttrs().hasAttribute<LazyAttr>()) {
auto *varDecl = cast<VarDecl>(storage);
auto *bindingDecl = varDecl->getParentPatternBinding();
auto *bindingInit = cast<PatternBindingInitializer>(
bindingDecl->getPatternEntryForVarDecl(varDecl).getInitContext());

selfDecl = bindingInit->getImplicitSelfDecl();
} else {
selfDecl = ParamDecl::createSelf(loc,
storage->getDeclContext(),
/*isStatic*/false);
}

getterParams.push_back(ParameterList::create(TC.Context, selfDecl));
}

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


Pattern *Tmp2PBDPattern = new (Ctx) NamedPattern(Tmp2VD, /*implicit*/true);
Tmp2PBDPattern = new (Ctx) TypedPattern(Tmp2PBDPattern,
TypeLoc::withoutLoc(VD->getType()),
Expand Down Expand Up @@ -1673,10 +1688,6 @@ void swift::maybeAddMaterializeForSet(AbstractStorageDecl *storage,
addMaterializeForSet(storage, TC);
}

void TypeChecker::introduceLazyVarAccessors(VarDecl *var) {
maybeAddAccessorsToVariable(var, *this);
}

void swift::maybeAddAccessorsToVariable(VarDecl *var, TypeChecker &TC) {
if (var->getGetter())
return;
Expand Down
3 changes: 0 additions & 3 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -1229,9 +1229,6 @@ class TypeChecker final : public LazyResolver {
handleExternalDecl(nominal);
}

/// Introduce the accessors for a 'lazy' variable.
void introduceLazyVarAccessors(VarDecl *var) override;

/// Infer default value witnesses for all requirements in the given protocol.
void inferDefaultWitnesses(ProtocolDecl *proto);

Expand Down
2 changes: 1 addition & 1 deletion test/NameBinding/name_lookup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ class r21677702 {
// <rdar://problem/16954496> lazy properties must use "self." in their body, and can weirdly refer to class variables directly
class r16954496 {
func bar() {}
lazy var x: Array<(r16954496) -> () -> Void> = [bar]
lazy var x: Array<() -> Void> = [bar]
}


Expand Down
3 changes: 1 addition & 2 deletions test/NameBinding/scope_map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,7 @@ class LazyProperties {
// CHECK-SEARCHES-NEXT: ClassDecl name=LazyProperties
// CHECK-SEARCHES-NEXT: Initializer PatternBinding {{.*}} #0

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

// CHECK-SEARCHES-LABEL: ***Complete scope map***
// CHECK-SEARCHES-NEXT: SourceFile {{.*}} '{{.*}}scope_map.swift' [1:1 - [[EOF:[0-9]+:[0-9]+]]] unexpanded
Expand Down
38 changes: 36 additions & 2 deletions test/decl/var/lazy_properties.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %target-typecheck-verify-swift -parse-as-library
// RUN: %target-typecheck-verify-swift -parse-as-library -swift-version 3
// RUN: %target-typecheck-verify-swift -parse-as-library -swift-version 4

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

Expand Down Expand Up @@ -72,7 +73,8 @@ struct StructTest {

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

Expand Down Expand Up @@ -119,3 +121,35 @@ struct Construction {
class Constructor {
lazy var myQ = Construction(x: 3)
}


// Problems with self references
class BaseClass {
var baseInstanceProp = 42
static var baseStaticProp = 42
}

class ReferenceSelfInLazyProperty : BaseClass {
lazy var refs = (i, f())
lazy var trefs: (Int, Int) = (i, f())

lazy var qrefs = (self.i, self.f())
lazy var qtrefs: (Int, Int) = (self.i, self.f())

lazy var crefs = { (i, f()) }()
lazy var ctrefs: (Int, Int) = { (i, f()) }()

lazy var cqrefs = { (self.i, self.f()) }()
lazy var cqtrefs: (Int, Int) = { (self.i, self.f()) }()

lazy var mrefs = { () -> (Int, Int) in return (i, f()) }()
// expected-error@-1 {{call to method 'f' in closure requires explicit 'self.' to make capture semantics explicit}}
// expected-error@-2 {{reference to property 'i' in closure requires explicit 'self.' to make capture semantics explicit}}
lazy var mtrefs: (Int, Int) = { return (i, f()) }()

lazy var mqrefs = { () -> (Int, Int) in (self.i, self.f()) }()
lazy var mqtrefs: (Int, Int) = { return (self.i, self.f()) }()

var i = 42
func f() -> Int { return 0 }
}