Skip to content

Commit 1a79d93

Browse files
authored
Merge pull request #40702 from calda/cal--implicit-weak-self
[SE-0365] Allow implicit self for `weak self` captures
2 parents f810eb0 + 5bfdfd8 commit 1a79d93

File tree

12 files changed

+493
-68
lines changed

12 files changed

+493
-68
lines changed

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,48 @@ CHANGELOG
33

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

6+
## Swift 6.0
7+
8+
* [SE-0365][]:
9+
10+
Implicit `self` is now permitted for `weak self` captures, after `self` is unwrapped.
11+
12+
For example, the usage of implicit `self` below is now permitted:
13+
14+
```swift
15+
class ViewController {
16+
let button: Button
17+
18+
func setup() {
19+
button.tapHandler = { [weak self] in
20+
guard let self else { return }
21+
dismiss() // refers to `self.dismiss()`
22+
}
23+
}
24+
25+
func dismiss() { ... }
26+
}
27+
```
28+
29+
In Swift 5 language modes, implicit `self` is permitted for `weak self` captures in _non-escaping_ closures even before `self` is unwrapped. For example, this code compiles successfully in Swift 5 language mode:
30+
31+
```swift
32+
class ExampleClass {
33+
func makeArray() -> [String] {
34+
// `Array.map` takes a non-escaping closure:
35+
["foo", "bar", "baaz"].map { [weak self] string in
36+
double(string) // implicitly refers to `self!.double(string)`
37+
}
38+
}
39+
40+
func double(_ string: String) -> String {
41+
string + string
42+
}
43+
}
44+
```
45+
46+
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]`).
47+
648
## Swift 5.8
749

850
* [SE-0362][]:
@@ -9559,6 +9601,7 @@ Swift 1.0
95599601
[SE-0357]: <https://github.com/apple/swift-evolution/blob/main/proposals/0357-regex-string-processing-algorithms.md>
95609602
[SE-0358]: <https://github.com/apple/swift-evolution/blob/main/proposals/0358-primary-associated-types-in-stdlib.md>
95619603
[SE-0362]: <https://github.com/apple/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md>
9604+
[SE-0365]: <https://github.com/apple/swift-evolution/blob/main/proposals/0365-implicit-self-weak-capture.md>
95629605

95639606
[SR-75]: <https://bugs.swift.org/browse/SR-75>
95649607
[SR-106]: <https://bugs.swift.org/browse/SR-106>

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,12 @@ NOTE(optional_key_path_root_base_chain, none,
12041204
NOTE(optional_key_path_root_base_unwrap, none,
12051205
"unwrap the optional using '!.' to access unwrapped type member %0",
12061206
(DeclNameRef))
1207+
1208+
ERROR(optional_self_not_unwrapped,none,
1209+
"explicit use of 'self' is required when 'self' is optional, "
1210+
"to make control flow explicit", ())
1211+
NOTE(optional_self_chain,none,
1212+
"reference 'self?.' explicitly", ())
12071213

12081214
ERROR(missing_unwrap_optional_try,none,
12091215
"value of optional type %0 not unwrapped; did you mean to use 'try!' "
@@ -3939,8 +3945,6 @@ NOTE(note_reference_self_explicitly,none,
39393945
NOTE(note_other_self_capture,none,
39403946
"variable other than 'self' captured here under the name 'self' does not "
39413947
"enable implicit 'self'", ())
3942-
NOTE(note_self_captured_weakly,none,
3943-
"weak capture of 'self' here does not enable implicit 'self'", ())
39443948
ERROR(implicit_use_of_self_in_closure,none,
39453949
"implicit use of 'self' in closure; use 'self.' to make"
39463950
" capture semantics explicit", ())

include/swift/AST/NameLookup.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,20 @@ struct LookupResultEntry {
107107
/// extension (if it found something at that level).
108108
DeclContext *BaseDC;
109109

110+
/// The declaration that defines the base of the call to `Value`.
111+
/// This is always available, as long as `BaseDC` is not null.
112+
ValueDecl *BaseDecl;
113+
110114
/// The declaration corresponds to the given name; i.e. the decl we are
111115
/// looking up.
112116
ValueDecl *Value;
113117

114118
public:
115-
LookupResultEntry(ValueDecl *value) : BaseDC(nullptr), Value(value) {}
119+
LookupResultEntry(ValueDecl *value)
120+
: BaseDC(nullptr), BaseDecl(nullptr), Value(value) {}
116121

117-
LookupResultEntry(DeclContext *baseDC, ValueDecl *value)
118-
: BaseDC(baseDC), Value(value) {}
122+
LookupResultEntry(DeclContext *baseDC, ValueDecl *baseDecl, ValueDecl *value)
123+
: BaseDC(baseDC), BaseDecl(baseDecl), Value(value) {}
119124

120125
ValueDecl *getValueDecl() const { return Value; }
121126

include/swift/AST/Stmt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ class DoStmt : public LabeledStmt {
635635
};
636636

637637
/// Either an "if let" case or a simple boolean expression can appear as the
638-
/// condition of an 'if' or 'while' statement.
638+
/// condition of an 'if', 'guard', or 'while' statement.
639639
using StmtCondition = MutableArrayRef<StmtConditionElement>;
640640

641641
/// This is the common base class between statements that can have labels, and

lib/AST/NameLookup.cpp

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,7 @@ ValueDecl *LookupResultEntry::getBaseDecl() const {
5757
if (BaseDC == nullptr)
5858
return nullptr;
5959

60-
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(BaseDC))
61-
return AFD->getImplicitSelfDecl();
62-
63-
if (auto *PBI = dyn_cast<PatternBindingInitializer>(BaseDC)) {
64-
auto *selfDecl = PBI->getImplicitSelfDecl();
65-
assert(selfDecl);
66-
return selfDecl;
67-
}
68-
69-
if (auto *CE = dyn_cast<ClosureExpr>(BaseDC)) {
70-
auto *selfDecl = CE->getCapturedSelfDecl();
71-
assert(selfDecl);
72-
assert(selfDecl->isSelfParamCapture());
73-
return selfDecl;
74-
}
75-
76-
auto *nominalDecl = BaseDC->getSelfNominalTypeDecl();
77-
assert(nominalDecl);
78-
return nominalDecl;
60+
return BaseDecl;
7961
}
8062

8163
void LookupResult::filter(

lib/AST/UnqualifiedLookup.cpp

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/AST/ASTVisitor.h"
2020
#include "swift/AST/DebuggerClient.h"
2121
#include "swift/AST/ImportCache.h"
22+
#include "swift/AST/Initializer.h"
2223
#include "swift/AST/ModuleNameLookup.h"
2324
#include "swift/AST/NameLookup.h"
2425
#include "swift/AST/NameLookupRequests.h"
@@ -76,6 +77,8 @@ namespace {
7677

7778
private:
7879
SelfBounds findSelfBounds(const DeclContext *dc);
80+
ValueDecl *lookupBaseDecl(const DeclContext *baseDC) const;
81+
ValueDecl *getBaseDeclForResult(const DeclContext *baseDC) const;
7982

8083
// Classify this declaration.
8184
// Types are formally members of the metatype.
@@ -343,14 +346,84 @@ void UnqualifiedLookupFactory::ResultFinderForTypeContext::findResults(
343346
SmallVector<ValueDecl *, 4> Lookup;
344347
contextForLookup->lookupQualified(selfBounds, Name, baseNLOptions, Lookup);
345348
for (auto Result : Lookup) {
346-
results.emplace_back(const_cast<DeclContext *>(whereValueIsMember(Result)),
347-
Result);
349+
auto baseDC = const_cast<DeclContext *>(whereValueIsMember(Result));
350+
auto baseDecl = getBaseDeclForResult(baseDC);
351+
results.emplace_back(baseDC, baseDecl, Result);
348352
#ifndef NDEBUG
349353
factory->addedResult(results.back());
350354
#endif
351355
}
352356
}
353357

358+
ValueDecl *
359+
UnqualifiedLookupFactory::ResultFinderForTypeContext::getBaseDeclForResult(
360+
const DeclContext *baseDC) const {
361+
if (baseDC == nullptr) {
362+
return nullptr;
363+
}
364+
365+
if (auto localBaseDecl = lookupBaseDecl(baseDC)) {
366+
return localBaseDecl;
367+
}
368+
369+
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(baseDC)) {
370+
return const_cast<ParamDecl *>(AFD->getImplicitSelfDecl());
371+
}
372+
373+
if (auto *PBI = dyn_cast<PatternBindingInitializer>(baseDC)) {
374+
auto *selfDecl = PBI->getImplicitSelfDecl();
375+
assert(selfDecl);
376+
return selfDecl;
377+
}
378+
379+
else if (auto *CE = dyn_cast<ClosureExpr>(baseDC)) {
380+
auto *selfDecl = CE->getCapturedSelfDecl();
381+
assert(selfDecl);
382+
assert(selfDecl->isSelfParamCapture());
383+
return selfDecl;
384+
}
385+
386+
auto *nominalDecl = baseDC->getSelfNominalTypeDecl();
387+
assert(nominalDecl);
388+
return nominalDecl;
389+
}
390+
391+
ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
392+
const DeclContext *baseDC) const {
393+
// Perform an unqualified lookup for the base decl of this result. This
394+
// handles cases where self was rebound (e.g. `guard let self = self`)
395+
// earlier in the scope.
396+
//
397+
// Only do this in closures that capture self weakly, since implicit self
398+
// isn't allowed to be rebound in other contexts. In other contexts, implicit
399+
// self _always_ refers to the context's self `ParamDecl`, even if there
400+
// is another local decl with the name `self` that would be found by
401+
// `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+
}
408+
}
409+
}
410+
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)) {
415+
return nullptr;
416+
}
417+
418+
if (isInWeakSelfClosure) {
419+
return ASTScope::lookupSingleLocalDecl(factory->DC->getParentSourceFile(),
420+
DeclName(factory->Ctx.Id_self),
421+
factory->Loc);
422+
}
423+
424+
return nullptr;
425+
}
426+
354427
// TODO (someday): Instead of adding unavailable entries to Results,
355428
// then later shunting them aside, just put them in the right place
356429
// to begin with.

lib/Sema/CSDiagnostics.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,22 @@ bool MemberAccessOnOptionalBaseFailure::diagnoseAsError() {
13711371
.fixItInsert(sourceRange.End, "!.");
13721372
}
13731373
} else {
1374+
// Check whether or not the base of this optional unwrap is implicit self
1375+
// This can only happen with a [weak self] capture, and is not permitted.
1376+
if (auto dotExpr = getAsExpr<UnresolvedDotExpr>(locator->getAnchor())) {
1377+
if (auto baseDeclRef = dyn_cast<DeclRefExpr>(dotExpr->getBase())) {
1378+
ASTContext &Ctx = baseDeclRef->getDecl()->getASTContext();
1379+
if (baseDeclRef->isImplicit() &&
1380+
baseDeclRef->getDecl()->getName().isSimpleName(Ctx.Id_self)) {
1381+
emitDiagnostic(diag::optional_self_not_unwrapped);
1382+
1383+
emitDiagnostic(diag::optional_self_chain)
1384+
.fixItInsertAfter(sourceRange.End, "self?.");
1385+
return true;
1386+
}
1387+
}
1388+
}
1389+
13741390
emitDiagnostic(diag::optional_base_not_unwrapped, baseType, Member,
13751391
unwrappedBaseType);
13761392

0 commit comments

Comments
 (0)