Skip to content

Commit a784c00

Browse files
authored
Merge pull request #61520 from calda/cal--SE-0365-swift-5
Enable SE-0365 behavior in Swift 5 mode
2 parents 6bd387e + c081d56 commit a784c00

File tree

7 files changed

+272
-87
lines changed

7 files changed

+272
-87
lines changed

CHANGELOG.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
_**Note:** This is in reverse chronological order, so newer entries are added to the top._
44

5-
## Swift 6.0
5+
## Swift 5.8
66

77
* [SE-0365][]:
88

99
Implicit `self` is now permitted for `weak self` captures, after `self` is unwrapped.
1010

11-
For example, the usage of implicit `self` below is now permitted:
11+
For example, the usage of implicit `self` below is permitted:
1212

1313
```swift
1414
class ViewController {
@@ -44,8 +44,6 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
4444

4545
In Swift 6, the above code will no longer compile. `weak self` captures in non-escaping closures now have the same behavior as captures in escaping closures (as described in [SE-0365][]). Code relying on the previous behavior will need to be updated to either unwrap `self` (e.g. by adding a `guard let self else return` statement), or to use a different capture method (e.g. using `[self]` or `[unowned self]` instead of `[weak self]`).
4646

47-
## Swift 5.8
48-
4947
* [SE-0362][]:
5048

5149
The compiler flag `-enable-upcoming-feature X` can now be used to enable a specific feature `X` that has been accepted by the evolution process, but whose introduction into the language is waiting for the next major version (e.g., version 6). The `X` is specified by any proposal that falls into this category:

lib/AST/UnqualifiedLookup.cpp

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,43 @@ UnqualifiedLookupFactory::ResultFinderForTypeContext::getBaseDeclForResult(
388388
return nominalDecl;
389389
}
390390

391+
/// Whether or not the given self decl is defined in an optional
392+
/// unwrapping condition (e.g. `guard let self else { return }`).
393+
/// If this is true, then we know any implicit self reference in the
394+
/// following scope is guaranteed to be non-optional.
395+
bool implicitSelfReferenceIsUnwrapped(const ValueDecl *selfDecl,
396+
const AbstractClosureExpr *inClosure) {
397+
ASTContext &Ctx = selfDecl->getASTContext();
398+
399+
// Check if the implicit self decl refers to a var in a conditional stmt
400+
LabeledConditionalStmt *conditionalStmt = nullptr;
401+
if (auto var = dyn_cast<VarDecl>(selfDecl)) {
402+
if (auto parentStmt = var->getParentPatternStmt()) {
403+
conditionalStmt = dyn_cast<LabeledConditionalStmt>(parentStmt);
404+
}
405+
}
406+
407+
if (!conditionalStmt) {
408+
return false;
409+
}
410+
411+
// Find the condition that defined the self decl,
412+
// and check that both its LHS and RHS are 'self'
413+
for (auto cond : conditionalStmt->getCond()) {
414+
if (auto pattern = cond.getPattern()) {
415+
if (pattern->getBoundName() != Ctx.Id_self) {
416+
continue;
417+
}
418+
}
419+
420+
if (auto selfDRE = dyn_cast<DeclRefExpr>(cond.getInitializer())) {
421+
return (selfDRE->getDecl()->getName().isSimpleName(Ctx.Id_self));
422+
}
423+
}
424+
425+
return false;
426+
}
427+
391428
ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
392429
const DeclContext *baseDC) const {
393430
// Perform an unqualified lookup for the base decl of this result. This
@@ -399,29 +436,59 @@ ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
399436
// self _always_ refers to the context's self `ParamDecl`, even if there
400437
// is another local decl with the name `self` that would be found by
401438
// `lookupSingleLocalDecl`.
402-
bool isInWeakSelfClosure = false;
403-
if (auto closureExpr = dyn_cast<ClosureExpr>(factory->DC)) {
404-
if (auto decl = closureExpr->getCapturedSelfDecl()) {
405-
if (auto a = decl->getAttrs().getAttribute<ReferenceOwnershipAttr>()) {
406-
isInWeakSelfClosure = a->get() == ReferenceOwnership::Weak;
407-
}
439+
auto closureExpr = dyn_cast<ClosureExpr>(factory->DC);
440+
if (!closureExpr) {
441+
return nullptr;
442+
}
443+
444+
bool capturesSelfWeakly = false;
445+
if (auto decl = closureExpr->getCapturedSelfDecl()) {
446+
if (auto a = decl->getAttrs().getAttribute<ReferenceOwnershipAttr>()) {
447+
capturesSelfWeakly = a->get() == ReferenceOwnership::Weak;
408448
}
409449
}
410450

411-
// We can only change the behavior of lookup in Swift 6 and later,
412-
// due to a bug in Swift 5 where implicit self is always allowed
413-
// for weak self captures in non-escaping closures.
414-
if (!factory->Ctx.LangOpts.isSwiftVersionAtLeast(6)) {
451+
if (!capturesSelfWeakly) {
415452
return nullptr;
416453
}
417454

418-
if (isInWeakSelfClosure) {
419-
return ASTScope::lookupSingleLocalDecl(factory->DC->getParentSourceFile(),
420-
DeclName(factory->Ctx.Id_self),
421-
factory->Loc);
455+
auto selfDecl = ASTScope::lookupSingleLocalDecl(
456+
factory->DC->getParentSourceFile(), DeclName(factory->Ctx.Id_self),
457+
factory->Loc);
458+
459+
if (!selfDecl) {
460+
return nullptr;
461+
}
462+
463+
// In Swift 5 mode, implicit self is allowed within non-escaping
464+
// closures even before self is unwrapped. For example, this is allowed:
465+
//
466+
// doVoidStuffNonEscaping { [weak self] in
467+
// method() // implicitly `self.method()`
468+
// }
469+
//
470+
// To support this, we have to preserve the lookup behavior from
471+
// Swift 5.7 and earlier where implicit self defaults to the closure's
472+
// `ParamDecl`. This causes the closure to capture self strongly, however,
473+
// which is not acceptable for escaping closures.
474+
//
475+
// Escaping closures, however, only need to permit implicit self once
476+
// it has been unwrapped to be non-optional:
477+
//
478+
// doVoidStuffEscaping { [weak self] in
479+
// guard let self else { return }
480+
// method()
481+
// }
482+
//
483+
// In these cases, using the Swift 6 lookup behavior doesn't affect
484+
// how the body is type-checked, so it can be used in Swift 5 mode
485+
// without breaking source compatibility for non-escaping closures.
486+
if (!factory->Ctx.LangOpts.isSwiftVersionAtLeast(6) &&
487+
!implicitSelfReferenceIsUnwrapped(selfDecl, closureExpr)) {
488+
return nullptr;
422489
}
423490

424-
return nullptr;
491+
return selfDecl;
425492
}
426493

427494
// TODO (someday): Instead of adding unavailable entries to Results,

0 commit comments

Comments
 (0)