Skip to content

Diagnose the implicit use of self in nested closures #35898

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 1 commit into from
Feb 12, 2021
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
29 changes: 23 additions & 6 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1461,14 +1461,31 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
Closures.push_back(ACE);
}

static bool isEnclosingSelfReference(VarDecl *var,
const AbstractClosureExpr *inClosure) {
if (var->isSelfParameter())
return true;

// Capture variables have a DC of the parent function.
if (inClosure && var->isSelfParamCapture() &&
var->getDeclContext() != inClosure->getParent())
return true;

return false;
}

/// Return true if this is an implicit reference to self which is required
/// to be explicit in an escaping closure. Metatype references and value
/// type references are excluded.
static bool isImplicitSelfParamUseLikelyToCauseCycle(Expr *E) {
static bool isImplicitSelfParamUseLikelyToCauseCycle(Expr *E,
const AbstractClosureExpr *inClosure) {
auto *DRE = dyn_cast<DeclRefExpr>(E);

if (!DRE || !DRE->isImplicit() || !isa<VarDecl>(DRE->getDecl()) ||
!cast<VarDecl>(DRE->getDecl())->isSelfParameter())
if (!DRE || !DRE->isImplicit())
return false;

auto var = dyn_cast<VarDecl>(DRE->getDecl());
if (!var || !isEnclosingSelfReference(var, inClosure))
return false;

// Defensive check for type. If the expression doesn't have type here, it
Expand Down Expand Up @@ -1544,7 +1561,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,

SourceLoc memberLoc = SourceLoc();
if (auto *MRE = dyn_cast<MemberRefExpr>(E))
if (isImplicitSelfParamUseLikelyToCauseCycle(MRE->getBase())) {
if (isImplicitSelfParamUseLikelyToCauseCycle(MRE->getBase(), ACE)) {
auto baseName = MRE->getMember().getDecl()->getBaseName();
memberLoc = MRE->getLoc();
Diags.diagnose(memberLoc,
Expand All @@ -1554,7 +1571,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,

// Handle method calls with a specific diagnostic + fixit.
if (auto *DSCE = dyn_cast<DotSyntaxCallExpr>(E))
if (isImplicitSelfParamUseLikelyToCauseCycle(DSCE->getBase()) &&
if (isImplicitSelfParamUseLikelyToCauseCycle(DSCE->getBase(), ACE) &&
isa<DeclRefExpr>(DSCE->getFn())) {
auto MethodExpr = cast<DeclRefExpr>(DSCE->getFn());
memberLoc = DSCE->getLoc();
Expand All @@ -1569,7 +1586,7 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
}

// Catch any other implicit uses of self with a generic diagnostic.
if (isImplicitSelfParamUseLikelyToCauseCycle(E))
if (isImplicitSelfParamUseLikelyToCauseCycle(E, ACE))
Diags.diagnose(E->getLoc(), diag::implicit_use_of_self_in_closure);

return { true, E };
Expand Down
61 changes: 61 additions & 0 deletions test/expr/closure/closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ func anonymousClosureArgsInClosureWithArgs() {

func doStuff(_ fn : @escaping () -> Int) {}
func doVoidStuff(_ fn : @escaping () -> ()) {}
func doVoidStuffNonEscaping(_ fn: () -> ()) {}

// <rdar://problem/16193162> Require specifying self for locations in code where strong reference cycles are likely
class ExplicitSelfRequiredTest {
Expand Down Expand Up @@ -528,3 +529,63 @@ class SR3186 {
print("\(v)")
}
}

// Apply the explicit 'self' rule even if it referrs to a capture, if
// we're inside a nested closure
class SR14120 {
func operation() {}

func test1() {
doVoidStuff { [self] in
operation()
}
}

func test2() {
doVoidStuff { [self] in
doVoidStuff {
// expected-error@+3 {{call to method 'operation' in closure requires explicit use of 'self'}}
// expected-note@-2 {{capture 'self' explicitly to enable implicit 'self' in this closure}}
// expected-note@+1 {{reference 'self.' explicitly}}
operation()
}
}
}

func test3() {
doVoidStuff { [self] in
doVoidStuff { [self] in
operation()
}
}
}

func test4() {
doVoidStuff { [self] in
doVoidStuff {
self.operation()
}
}
}

func test5() {
doVoidStuff { [self] in
doVoidStuffNonEscaping {
operation()
}
}
}

func test6() {
doVoidStuff { [self] in
doVoidStuff { [self] in
doVoidStuff {
// expected-error@+3 {{call to method 'operation' in closure requires explicit use of 'self'}}
// expected-note@-2 {{capture 'self' explicitly to enable implicit 'self' in this closure}}
// expected-note@+1 {{reference 'self.' explicitly}}
operation()
}
}
}
}
}