Skip to content

Commit 18b747c

Browse files
committed
[Concurrency] Diagnose captures of self in a task created in deinit.
This is done by diagnosing captures of `self` in escaping `sending` or `@Sendable` closures inside a deinit, which almost certainly means `self` will outlive deinit at runtime, which is a fatal error. This is a common mistake to make when creating isolated tasks inside nonisolated deinits to workaround the lack of synchrnous isolated deinits in Swift 6.
1 parent e2ddc6c commit 18b747c

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5560,6 +5560,9 @@ ERROR(non_sendable_isolated_capture,none,
55605560
ERROR(implicit_async_let_non_sendable_capture,none,
55615561
"capture of %1 with non-sendable type %0 in 'async let' binding",
55625562
(Type, DeclName))
5563+
ERROR(self_capture_deinit_task,none,
5564+
"capture of 'self' in a closure that outlives deinit",
5565+
())
55635566
ERROR(implicit_non_sendable_capture,none,
55645567
"implicit capture of %1 requires that %0 conforms to `Sendable`",
55655568
(Type, DeclName))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2582,6 +2582,25 @@ namespace {
25822582
if (capture.isOpaqueValue())
25832583
continue;
25842584

2585+
auto *closure = localFunc.getAbstractClosureExpr();
2586+
2587+
// Diagnose a `self` capture inside an escaping `sending`
2588+
// `@Sendable` closure in a deinit, which almost certainly
2589+
// means `self` would escape deinit at runtime.
2590+
auto *explicitClosure = dyn_cast_or_null<ClosureExpr>(closure);
2591+
auto *dc = getDeclContext();
2592+
if (explicitClosure && isa<DestructorDecl>(dc) &&
2593+
!explicitClosure->getType()->isNoEscape() &&
2594+
(explicitClosure->isPassedToSendingParameter() ||
2595+
explicitClosure->isSendable())) {
2596+
auto var = dyn_cast_or_null<VarDecl>(capture.getDecl());
2597+
if (var && var->isSelfParameter()) {
2598+
ctx.Diags.diagnose(explicitClosure->getLoc(),
2599+
diag::self_capture_deinit_task)
2600+
.warnUntilSwiftVersion(6);
2601+
}
2602+
}
2603+
25852604
// If the closure won't execute concurrently with the context in
25862605
// which the declaration occurred, it's okay.
25872606
auto decl = capture.getDecl();
@@ -2606,7 +2625,6 @@ namespace {
26062625
if (type->hasError())
26072626
continue;
26082627

2609-
auto *closure = localFunc.getAbstractClosureExpr();
26102628
if (closure && closure->isImplicit()) {
26112629
auto *patternBindingDecl = getTopPatternBindingDecl();
26122630
if (patternBindingDecl && patternBindingDecl->isAsyncLet()) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking
2+
3+
@MainActor
4+
class C {
5+
let x: Int = 0
6+
7+
deinit {
8+
// expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}}
9+
Task { @MainActor in
10+
_ = self
11+
}
12+
13+
// expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}}
14+
Task {
15+
_ = x
16+
}
17+
}
18+
}
19+
20+
func enqueueSomewhereElse(_ closure: @escaping @Sendable () -> Void) {}
21+
22+
@MainActor
23+
class C2 {
24+
let x: Int = 0
25+
26+
deinit {
27+
// expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}}
28+
enqueueSomewhereElse {
29+
_ = self
30+
}
31+
32+
// expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}}
33+
enqueueSomewhereElse {
34+
_ = self.x
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)