diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 76627e870563c..ffd28324f115b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -5520,6 +5520,9 @@ ERROR(non_sendable_isolated_capture,none, ERROR(implicit_async_let_non_sendable_capture,none, "capture of %1 with non-sendable type %0 in 'async let' binding", (Type, DeclName)) +ERROR(self_capture_deinit_task,none, + "capture of 'self' in a closure that outlives deinit", + ()) ERROR(implicit_non_sendable_capture,none, "implicit capture of %1 requires that %0 conforms to `Sendable`", (Type, DeclName)) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index e50768b313fec..acb578d128a95 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -2793,6 +2793,25 @@ namespace { if (capture.isOpaqueValue()) continue; + auto *closure = localFunc.getAbstractClosureExpr(); + + // Diagnose a `self` capture inside an escaping `sending` + // `@Sendable` closure in a deinit, which almost certainly + // means `self` would escape deinit at runtime. + auto *explicitClosure = dyn_cast_or_null(closure); + auto *dc = getDeclContext(); + if (explicitClosure && isa(dc) && + !explicitClosure->getType()->isNoEscape() && + (explicitClosure->isPassedToSendingParameter() || + explicitClosure->isSendable())) { + auto var = dyn_cast_or_null(capture.getDecl()); + if (var && var->isSelfParameter()) { + ctx.Diags.diagnose(explicitClosure->getLoc(), + diag::self_capture_deinit_task) + .warnUntilSwiftVersion(6); + } + } + // If the closure won't execute concurrently with the context in // which the declaration occurred, it's okay. auto decl = capture.getDecl(); @@ -2817,7 +2836,6 @@ namespace { if (type->hasError()) continue; - auto *closure = localFunc.getAbstractClosureExpr(); if (closure && closure->isImplicit()) { auto *patternBindingDecl = getTopPatternBindingDecl(); if (patternBindingDecl && patternBindingDecl->isAsyncLet()) { diff --git a/test/Concurrency/self_escapes_deinit.swift b/test/Concurrency/self_escapes_deinit.swift new file mode 100644 index 0000000000000..352256dfdb861 --- /dev/null +++ b/test/Concurrency/self_escapes_deinit.swift @@ -0,0 +1,37 @@ +// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking + +@MainActor +class C { + let x: Int = 0 + + deinit { + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + Task { @MainActor in + _ = self + } + + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + Task { + _ = x + } + } +} + +func enqueueSomewhereElse(_ closure: @escaping @Sendable () -> Void) {} + +@MainActor +class C2 { + let x: Int = 0 + + deinit { + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + enqueueSomewhereElse { + _ = self + } + + // expected-warning@+1 {{capture of 'self' in a closure that outlives deinit; this is an error in the Swift 6 language mode}} + enqueueSomewhereElse { + _ = self.x + } + } +}