Skip to content

Commit 8c5f783

Browse files
committed
[SE-0365] Fix issue where implicit self was unexpectedly disallowed in nested closures
1 parent 1a84094 commit 8c5f783

File tree

5 files changed

+754
-106
lines changed

5 files changed

+754
-106
lines changed

lib/AST/UnqualifiedLookup.cpp

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,7 @@ UnqualifiedLookupFactory::ResultFinderForTypeContext::getBaseDeclForResult(
393393
/// unwrapping condition (e.g. `guard let self else { return }`).
394394
/// If this is true, then we know any implicit self reference in the
395395
/// following scope is guaranteed to be non-optional.
396-
bool implicitSelfReferenceIsUnwrapped(const ValueDecl *selfDecl,
397-
const AbstractClosureExpr *inClosure) {
396+
bool implicitSelfReferenceIsUnwrapped(const ValueDecl *selfDecl) {
398397
ASTContext &Ctx = selfDecl->getASTContext();
399398

400399
// Check if the implicit self decl refers to a var in a conditional stmt
@@ -438,29 +437,12 @@ ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
438437
const DeclContext *baseDC) const {
439438
// Perform an unqualified lookup for the base decl of this result. This
440439
// handles cases where self was rebound (e.g. `guard let self = self`)
441-
// earlier in the scope.
442-
//
443-
// Only do this in closures that capture self weakly, since implicit self
444-
// isn't allowed to be rebound in other contexts. In other contexts, implicit
445-
// self _always_ refers to the context's self `ParamDecl`, even if there
446-
// is another local decl with the name `self` that would be found by
447-
// `lookupSingleLocalDecl`.
440+
// earlier in this closure or some outer closure.
448441
auto closureExpr = closestParentClosure(factory->DC);
449442
if (!closureExpr) {
450443
return nullptr;
451444
}
452445

453-
bool capturesSelfWeakly = false;
454-
if (auto decl = closureExpr->getCapturedSelfDecl()) {
455-
if (auto a = decl->getAttrs().getAttribute<ReferenceOwnershipAttr>()) {
456-
capturesSelfWeakly = a->get() == ReferenceOwnership::Weak;
457-
}
458-
}
459-
460-
if (!capturesSelfWeakly) {
461-
return nullptr;
462-
}
463-
464446
auto selfDecl = ASTScope::lookupSingleLocalDecl(
465447
factory->DC->getParentSourceFile(), DeclName(factory->Ctx.Id_self),
466448
factory->Loc);
@@ -469,16 +451,24 @@ ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
469451
return nullptr;
470452
}
471453

454+
bool capturesSelfWeakly = false;
455+
if (auto decl = closureExpr->getCapturedSelfDecl()) {
456+
if (auto a = decl->getAttrs().getAttribute<ReferenceOwnershipAttr>()) {
457+
capturesSelfWeakly = a->get() == ReferenceOwnership::Weak;
458+
}
459+
}
460+
472461
// In Swift 5 mode, implicit self is allowed within non-escaping
473-
// closures even before self is unwrapped. For example, this is allowed:
462+
// `weak self` closures even before self is unwrapped.
463+
// For example, this is allowed:
474464
//
475465
// doVoidStuffNonEscaping { [weak self] in
476466
// method() // implicitly `self.method()`
477467
// }
478468
//
479469
// To support this, we have to preserve the lookup behavior from
480470
// Swift 5.7 and earlier where implicit self defaults to the closure's
481-
// `ParamDecl`. This causes the closure to capture self strongly, however,
471+
// `ParamDecl`. This causes the closure to capture self strongly,
482472
// which is not acceptable for escaping closures.
483473
//
484474
// Escaping closures, however, only need to permit implicit self once
@@ -492,11 +482,29 @@ ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
492482
// In these cases, using the Swift 6 lookup behavior doesn't affect
493483
// how the body is type-checked, so it can be used in Swift 5 mode
494484
// without breaking source compatibility for non-escaping closures.
495-
if (!factory->Ctx.LangOpts.isSwiftVersionAtLeast(6) &&
496-
!implicitSelfReferenceIsUnwrapped(selfDecl, closureExpr)) {
485+
if (capturesSelfWeakly && !factory->Ctx.LangOpts.isSwiftVersionAtLeast(6) &&
486+
!implicitSelfReferenceIsUnwrapped(selfDecl)) {
497487
return nullptr;
498488
}
499489

490+
// Closures are only allowed to rebind self in specific circumstances:
491+
// 1. In a capture list with an explicit capture.
492+
// 2. In a `guard let self = self` / `if let self = self` condition
493+
// in a closure that captures self weakly.
494+
//
495+
// These rebindings can be done by any parent closure, and apply
496+
// to all nested closures. We only need to check these structural
497+
// requirements loosely here -- more extensive validation happens
498+
// later in MiscDiagnostics::diagnoseImplicitSelfUseInClosure.
499+
//
500+
// Other types of rebindings, like an arbitrary "let `self` = foo",
501+
// are never allowed to rebind self.
502+
if (auto var = dyn_cast<VarDecl>(selfDecl)) {
503+
if (!(var->isCaptureList() || var->getParentPatternStmt())) {
504+
return nullptr;
505+
}
506+
}
507+
500508
return selfDecl;
501509
}
502510

0 commit comments

Comments
 (0)