Skip to content

Commit 94ef6c4

Browse files
committed
Permit implicit self for weak self captures in nonescaping closures in Swift 5 (this is an error in Swift 6)
1 parent 5946c66 commit 94ef6c4

File tree

5 files changed

+209
-35
lines changed

5 files changed

+209
-35
lines changed

include/swift/AST/Pattern.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ class OptionalSomePattern : public Pattern {
608608
Pattern *SubPattern;
609609
SourceLoc QuestionLoc;
610610
EnumElementDecl *ElementDecl = nullptr;
611+
bool EnablesImplicitSelfForWeakSelfCapture = false;
611612

612613
public:
613614
explicit OptionalSomePattern(Pattern *SubPattern,
@@ -627,6 +628,17 @@ class OptionalSomePattern : public Pattern {
627628
EnumElementDecl *getElementDecl() const { return ElementDecl; }
628629
void setElementDecl(EnumElementDecl *d) { ElementDecl = d; }
629630

631+
/// Whether or not this pattern is used to enable implicit self
632+
/// for weak self captures in the following scope. This is used
633+
/// in Swift 5 language modes to prevent a false-positive
634+
/// "unused value" warning, and is not necessary in Swift 6+.
635+
bool getEnablesImplicitSelfForWeakSelfCapture() {
636+
return EnablesImplicitSelfForWeakSelfCapture;
637+
}
638+
void setEnablesImplicitSelfForWeakSelfCapture(bool value) {
639+
EnablesImplicitSelfForWeakSelfCapture = value;
640+
}
641+
630642
static bool classof(const Pattern *P) {
631643
return P->getKind() == PatternKind::OptionalSome;
632644
}

lib/AST/UnqualifiedLookup.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,13 @@ ValueDecl *UnqualifiedLookupFactory::ResultFinderForTypeContext::lookupBaseDecl(
408408
}
409409
}
410410

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+
411418
if (isInWeakSelfClosure) {
412419
return ASTScope::lookupSingleLocalDecl(factory->DC->getParentSourceFile(),
413420
DeclName(factory->Ctx.Id_self),

lib/Sema/MiscDiagnostics.cpp

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,7 +1581,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
15811581
// someUnrelatedVariable`. If self hasn't been unwrapped yet and is still
15821582
// an optional, we would have already hit an error elsewhere.
15831583
if (closureHasWeakSelfCapture(inClosure)) {
1584-
return !implicitWeakSelfReferenceIsValid(DRE);
1584+
return !implicitWeakSelfReferenceIsValid(DRE, inClosure);
15851585
}
15861586

15871587
// Defensive check for type. If the expression doesn't have type here, it
@@ -1620,12 +1620,32 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
16201620
return true;
16211621
}
16221622

1623-
static bool implicitWeakSelfReferenceIsValid(const DeclRefExpr *DRE) {
1623+
static bool
1624+
implicitWeakSelfReferenceIsValid(const DeclRefExpr *DRE,
1625+
const AbstractClosureExpr *inClosure) {
16241626
ASTContext &Ctx = DRE->getDecl()->getASTContext();
1627+
auto selfDecl = DRE->getDecl();
1628+
1629+
if (!Ctx.LangOpts.isSwiftVersionAtLeast(6)) {
1630+
// In Swift 5 language modes, the decl of this implicit `DeclRefExpr`
1631+
// incorrectly refers to the `self` `ParamDecl` of the closure
1632+
// (which is always non-optional), instead of the actual decl that
1633+
// an explicit `self` call would refer to. As a workaround, we can
1634+
// manually lookup what self "should" refer to, and then validate
1635+
// that decl instead.
1636+
auto lookupResult = ASTScope::lookupSingleLocalDecl(
1637+
inClosure->getParentSourceFile(), DeclName(Ctx.Id_self),
1638+
DRE->getLoc());
1639+
if (!lookupResult) {
1640+
return false;
1641+
}
1642+
1643+
selfDecl = lookupResult;
1644+
}
16251645

16261646
// Check if the implicit self decl refers to a var in a conditional stmt
16271647
LabeledConditionalStmt *conditionalStmt = nullptr;
1628-
if (auto var = dyn_cast<VarDecl>(DRE->getDecl())) {
1648+
if (auto var = dyn_cast<VarDecl>(selfDecl)) {
16291649
if (auto parentStmt = var->getParentPatternStmt()) {
16301650
conditionalStmt = dyn_cast<LabeledConditionalStmt>(parentStmt);
16311651
}
@@ -1645,7 +1665,17 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
16451665

16461666
if (auto LE = dyn_cast<LoadExpr>(cond.getInitializer())) {
16471667
if (auto selfDRE = dyn_cast<DeclRefExpr>(LE->getSubExpr())) {
1648-
return (selfDRE->getDecl()->getName().isSimpleName(Ctx.Id_self));
1668+
if (selfDRE->getDecl()->getName().isSimpleName(Ctx.Id_self)) {
1669+
// Mark this `OptionalStatementPattern` as enabling implicit
1670+
// self for this weak self capture. This lets us avoid emitting
1671+
// a false-positive warning in `VarDeclUsageChecker` in
1672+
// Swift 5 mode (this is unnecessary in Swift 6).
1673+
if (!Ctx.LangOpts.isSwiftVersionAtLeast(6)) {
1674+
OSP->setEnablesImplicitSelfForWeakSelfCapture(true);
1675+
}
1676+
1677+
return true;
1678+
}
16491679
}
16501680
}
16511681
}
@@ -1666,14 +1696,8 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
16661696

16671697
/// Return true if this is a closure expression that will require explicit
16681698
/// use or capture of "self." for qualification of member references.
1669-
static bool isClosureRequiringSelfQualification(
1670-
const AbstractClosureExpr *CE) {
1671-
// If this closure capture self weakly, then we have to validate each
1672-
// usage of implicit self individually, even in a nonescaping closure
1673-
if (closureHasWeakSelfCapture(CE)) {
1674-
return true;
1675-
}
1676-
1699+
static bool
1700+
isClosureRequiringSelfQualification(const AbstractClosureExpr *CE) {
16771701
// If the closure's type was inferred to be noescape, then it doesn't
16781702
// need qualification.
16791703
if (AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE))
@@ -1702,8 +1726,11 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
17021726
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
17031727
if (auto *CE = dyn_cast<AbstractClosureExpr>(E)) {
17041728
// If this is a potentially-escaping closure expression, start looking
1705-
// for references to self if we aren't already.
1706-
if (isClosureRequiringSelfQualification(CE))
1729+
// for references to self if we aren't already. But if this closure
1730+
// captures self weakly, then we have to validate each usage of implicit
1731+
// self individually, even in a nonescaping closure
1732+
if (isClosureRequiringSelfQualification(CE) ||
1733+
closureHasWeakSelfCapture(CE))
17071734
Closures.push_back(CE);
17081735
}
17091736

@@ -1724,7 +1751,10 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
17241751
// weakly, then we should always emit an error, since implicit self was
17251752
// only allowed starting in Swift 5.8 and later.
17261753
if (closureHasWeakSelfCapture(ACE)) {
1727-
return false;
1754+
// Implicit self was incorrectly permitted for weak self captures
1755+
// in non-escaping closures in Swift 5.7, so in that case we can
1756+
// only warn until Swift 6.
1757+
return !isClosureRequiringSelfQualification(ACE);
17281758
}
17291759

17301760
// We know that isImplicitSelfParamUseLikelyToCauseCycle is true,
@@ -3275,7 +3305,20 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
32753305
auto initExpr = SC->getCond()[0].getInitializer();
32763306
if (initExpr->getStartLoc().isValid()) {
32773307
unsigned noParens = initExpr->canAppendPostfixExpression();
3278-
3308+
3309+
// Don't emit an "unused value" warning if this is a
3310+
// `let self = self` condition being used to enable
3311+
// implicit self for the remainder of this scope,
3312+
// since it would be a false positive. This is only
3313+
// necessary in Swift 5 mode, because in Swift 6
3314+
// this new self decl is correctly used as the base of
3315+
// implicit self calls for the remainder of the scope.
3316+
auto &ctx = var->getASTContext();
3317+
if (!ctx.LangOpts.isSwiftVersionAtLeast(6) &&
3318+
OSP->getEnablesImplicitSelfForWeakSelfCapture()) {
3319+
continue;
3320+
}
3321+
32793322
// If the subexpr is an "as?" cast, we can rewrite it to
32803323
// be an "is" test.
32813324
ConditionalCheckedCastExpr *CCE = nullptr;

test/expr/closure/closures.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,12 @@ class ExplicitSelfRequiredTest {
261261
// because its `sawError` flag is set to true. To preserve the "capture 'y' was never used" warnings
262262
// above, we put these cases in their own method.
263263
func weakSelfError() {
264-
doVoidStuff({ [weak self] in x += 1 }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note {{reference 'self?.' explicitly}}
265-
doVoidStuffNonEscaping({ [weak self] in x += 1 }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note {{reference 'self?.' explicitly}}
266-
doStuff({ [weak self] in x+1 }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note {{reference 'self?.' explicitly}}
267-
doVoidStuff({ [weak self] in _ = method() }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note{{reference 'self?.' explicitly}}
268-
doVoidStuffNonEscaping({ [weak self] in _ = method() }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note{{reference 'self?.' explicitly}}
269-
doStuff({ [weak self] in method() }) // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note{{reference 'self?.' explicitly}}
264+
doVoidStuff({ [weak self] in x += 1 }) // expected-error {{reference to property 'x' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
265+
doVoidStuffNonEscaping({ [weak self] in x += 1 }) // expected-warning {{reference to property 'x' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
266+
doStuff({ [weak self] in x+1 }) // expected-error {{reference to property 'x' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
267+
doVoidStuff({ [weak self] in _ = method() }) // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
268+
doVoidStuffNonEscaping({ [weak self] in _ = method() }) // expected-warning {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
269+
doStuff({ [weak self] in method() }) // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-warning {{variable 'self' was written to, but never read}}
270270
}
271271
}
272272

@@ -742,12 +742,12 @@ public class TestImplicitCaptureOfExplicitCaptureOfSelfInEscapingClosure {
742742
}
743743

744744
public class TestImplicitSelfForWeakSelfCapture {
745-
static var staticOptional: TestImplicitSelfForWeakSelfCapture? = .init()
745+
static let staticOptional: TestImplicitSelfForWeakSelfCapture? = .init()
746746
func method() { }
747747

748748
private init() {
749749
doVoidStuff { [weak self] in
750-
method() // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note {{reference 'self?.' explicitly}}
750+
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
751751
guard let self = self else { return }
752752
method()
753753
}
@@ -759,19 +759,19 @@ public class TestImplicitSelfForWeakSelfCapture {
759759
}
760760

761761
doVoidStuff { [weak self] in
762-
guard let self = self else { return }
762+
guard let self = self else { return } // expected-warning {{value 'self' was defined but never used; consider replacing with boolean test}}
763763
doVoidStuff { // expected-note {{capture 'self' explicitly to enable implicit 'self' in this closure}}
764764
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-note {{reference 'self.' explicitly}}
765765
}
766766
}
767767

768768
doVoidStuff { [weak self] in
769-
guard let self = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional else { return }
769+
guard let self = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional else { return } // expected-warning {{value 'self' was defined but never used; consider replacing with boolean test}}
770770
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
771771
}
772772

773773
doVoidStuffNonEscaping { [weak self] in
774-
method() // expected-error {{explicit use of 'self' is required when 'self' is optional, to make control flow explicit}} expected-note {{reference 'self?.' explicitly}}
774+
method() // expected-warning {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
775775
guard let self = self else { return }
776776
method()
777777
}
@@ -784,26 +784,26 @@ public class TestImplicitSelfForWeakSelfCapture {
784784

785785
doVoidStuff { [weak self] in
786786
let `self`: TestImplicitSelfForWeakSelfCapture? = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional
787-
guard let self = self else { return }
787+
guard let self = self else { return } // // expected-warning {{value 'self' was defined but never used; consider replacing with boolean test}}
788788
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
789789
}
790790

791791
doVoidStuffNonEscaping { [weak self] in
792792
let `self`: TestImplicitSelfForWeakSelfCapture? = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional
793-
guard let self = self else { return }
794-
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
793+
guard let self = self else { return } // expected-warning {{unexpected warning produced: value 'self' was defined but never used; consider replacing with boolean test}}
794+
method() // expected-warning {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
795795
}
796796

797797
doVoidStuffNonEscaping { [weak self] in
798-
guard let self = self else { return }
798+
guard let self = self else { return } // expected-warning {{unexpected warning produced: value 'self' was defined but never used; consider replacing with boolean test}}
799799
doVoidStuff { // expected-note {{capture 'self' explicitly to enable implicit 'self' in this closure}}
800800
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}} expected-note {{reference 'self.' explicitly}}
801801
}
802802
}
803803

804804
doVoidStuffNonEscaping { [weak self] in
805-
guard let self = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional else { return }
806-
method() // expected-error {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
805+
guard let self = self ?? TestImplicitSelfForWeakSelfCapture.staticOptional else { return } // expected-warning {{unexpected warning produced: value 'self' was defined but never used; consider replacing with boolean test}}
806+
method() // expected-warning {{call to method 'method' in closure requires explicit use of 'self' to make capture semantics explicit}}
807807
}
808808
}
809809
}
@@ -824,12 +824,12 @@ public class TestRebindingSelfIsDisallowed {
824824

825825
doVoidStuff { [weak self] in
826826
let `self` = "self shouldn't become a string"
827-
let _: Int = count // expected-error{{reference to property 'count' in closure requires explicit use of 'self' to make capture semantics explicit}}
827+
let _: Int = count // expected-error{{cannot convert value of type 'Void' to specified type 'Int'}}
828828
}
829829

830830
doVoidStuffNonEscaping { [weak self] in
831831
let `self` = "self shouldn't become a string"
832-
let _: Int = count // expected-error{{reference to property 'count' in closure requires explicit use of 'self' to make capture semantics explicit}}
832+
let _: Int = count // expected-error{{cannot convert value of type 'Void' to specified type 'Int'}}
833833
}
834834
}
835835

0 commit comments

Comments
 (0)