From 76eae1232e45deb2ae73d40ca43d8d484fda7b63 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 19 Nov 2025 13:19:49 -0800 Subject: [PATCH] [Concurrency] Allow transferring `nonisolated(nonsending)` to isolation boundary closures Isolation boundary closures don't assume parent isolation and should be able to get `nonisolated(nonsending)` transferred to them jus like regular nonisolated closures do. Resolves: rdar://163792371 (cherry picked from commit 2335293f0d4da5c850bdf9923a3514918d191136) --- lib/Sema/TypeCheckConcurrency.cpp | 9 ++++--- .../attr_execution/conversions_silgen.swift | 26 +++++++++++++++++++ ...e_closureliterals_isolationinference.swift | 16 ++++++------ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 2f89d2d19daf2..7384053b377b8 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -5013,6 +5013,10 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation( auto normalIsolation = computeClosureIsolationFromParent( closure, parentIsolation, checkIsolatedCapture); + bool isIsolationBoundary = + isIsolationInferenceBoundaryClosure(closure, + /*canInheritActorContext=*/true); + // The solver has to be conservative and produce a conversion to // `nonisolated(nonsending)` because at solution application time // we don't yet know whether there are any captures which would @@ -5023,7 +5027,7 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation( // isolated parameters. If our closure is nonisolated and we have a // conversion to nonisolated(nonsending), then we should respect that. if (auto *explicitClosure = dyn_cast(closure); - !normalIsolation.isGlobalActor()) { + isIsolationBoundary || !normalIsolation.isGlobalActor()) { if (auto *fce = dyn_cast_or_null(Parent.getAsExpr())) { auto expectedIsolation = @@ -5042,8 +5046,7 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation( // NOTE: Since we already checked for global actor isolated things, we // know that all Sendable closures must be nonisolated. That is why it is // safe to rely on this path to handle Sendable closures. - if (isIsolationInferenceBoundaryClosure(closure, - /*canInheritActorContext=*/true)) + if (isIsolationBoundary) return ActorIsolation::forNonisolated(/*unsafe=*/false); return normalIsolation; diff --git a/test/Concurrency/attr_execution/conversions_silgen.swift b/test/Concurrency/attr_execution/conversions_silgen.swift index 8dc94ed8c4f1f..0117762be44f7 100644 --- a/test/Concurrency/attr_execution/conversions_silgen.swift +++ b/test/Concurrency/attr_execution/conversions_silgen.swift @@ -659,3 +659,29 @@ func testSendableToSendableConversionWithNonisilatedNonsending() { nonisolated(nonsending) @escaping (String) async throws -> String ) async throws -> Void = test } + +func testNonisolatedNonsendingClosureInGlobalActorContext() { + class NonSendable { + var state = "" + } + + struct S { + static func compute(closure: nonisolated(nonsending) @Sendable @escaping (sending NonSendable) async -> Void) async {} + } + + // CHECK-LABEL: sil private [ossa] @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaF : $@convention(thin) @async () -> () + // CHECK: [[CLOSURE:%.*]] = function_ref @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaFyAaByyF11NonSendableL_CYuYaYbYCcfU_ : $@convention(thin) @Sendable @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> () + // CHECK: [[THICK_CLOSURE:%.*]] = thin_to_thick_function [[CLOSURE]] to $@Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> () + // CHECK: [[CLOSURE_THUNK:%.*]] = function_ref @$sBA21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF11NonSendableL_CIeghHgILgT_BAADIeghHgILxT_TR : $@convention(thin) @Sendable @async (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable, @guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @guaranteed NonSendable) -> ()) -> () + // CHECK: [[THUNKED_CLOSURE:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE_THUNK]]([[THICK_CLOSURE]]) + // CHECK: [[COMPUTE:%.*]] = function_ref @$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF1SL_V7compute7closureyyAaByyF11NonSendableL_CnYuYaYbYCc_tYaFZ : $@convention(method) @async (@guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable) -> (), @thin S.Type) -> () + // CHECK: apply [[COMPUTE]]([[THUNKED_CLOSURE]], {{.*}}) : $@convention(method) @async (@guaranteed @Sendable @async @callee_guaranteed (@sil_isolated @sil_implicit_leading_param @guaranteed Builtin.ImplicitActor, @sil_sending @owned NonSendable) -> (), @thin S.Type) -> () + // CHECK: } // end sil function '$s21attr_execution_silgen52testNonisolatedNonsendingClosureInGlobalActorContextyyF0D0L_yyYaF' + @MainActor + func test() async { + // CHECK: // closure #1 in test #1 () in testNonisolatedNonsendingClosureInGlobalActorContext() + // CHECK: // Isolation: caller_isolation_inheriting + await S.compute { _ in + } + } +} diff --git a/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift b/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift index a4153bb3d3172..19e32f4de0754 100644 --- a/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift +++ b/test/Concurrency/transfernonsendable_closureliterals_isolationinference.swift @@ -143,11 +143,11 @@ func test_CallerSyncNormal_CalleeAsyncNonIsolated() async { normalAcceptsAsyncClosure { } // CHECK-LABEL: closure #2 in test_CallerSyncNormal_CalleeAsyncNonIsolated() - // CHECK-NEXT: Isolation: nonisolated + // CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}} normalAcceptsSendingAsyncClosure { } // CHECK-LABEL: // closure #3 in test_CallerSyncNormal_CalleeAsyncNonIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}} normalAcceptsSendableAsyncClosure { } } @@ -177,11 +177,11 @@ func test_CallerSyncNormal_CalleeAsyncMainActorIsolated() async { // expected-ni-ns-note @-4 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '@concurrent () async -> ()' to main actor-isolated global function 'normalGlobalActorAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and main actor-isolated uses}} // CHECK-LABEL: // closure #2 in test_CallerSyncNormal_CalleeAsyncMainActorIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}} await normalGlobalActorAcceptsSendingAsyncClosure { } // CHECK-LABEL: // closure #3 in test_CallerSyncNormal_CalleeAsyncMainActorIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}} await normalGlobalActorAcceptsSendableAsyncClosure { } } @@ -254,11 +254,11 @@ func test_CallerAsyncNormal_CalleeAsyncNonIsolated() async { // expected-ni-note @-1 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '() async -> ()' to nonisolated global function 'asyncNormalAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and nonisolated uses}} // CHECK-LABEL: closure #2 in test_CallerAsyncNormal_CalleeAsyncNonIsolated() - // CHECK-NEXT: Isolation: nonisolated + // CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}} await asyncNormalAcceptsSendingAsyncClosure { } // CHECK-LABEL: // closure #3 in test_CallerAsyncNormal_CalleeAsyncNonIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}} await asyncNormalAcceptsSendableAsyncClosure { } } @@ -296,11 +296,11 @@ func test_CallerAsyncNormal_CalleeAsyncMainActorIsolated() async { // expected-ni-ns-note @-4 {{sending global actor 'CustomActor'-isolated value of non-Sendable type '@concurrent () async -> ()' to main actor-isolated global function 'asyncNormalGlobalActorAcceptsAsyncClosure' risks causing races in between global actor 'CustomActor'-isolated and main actor-isolated uses}} // CHECK-LABEL: // closure #2 in test_CallerAsyncNormal_CalleeAsyncMainActorIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: Isolation: {{nonisolated|caller_isolation_inheriting}} await asyncNormalGlobalActorAcceptsSendingAsyncClosure { } // CHECK-LABEL: // closure #3 in test_CallerAsyncNormal_CalleeAsyncMainActorIsolated() - // CHECK-NEXT: // Isolation: nonisolated + // CHECK-NEXT: // Isolation: {{nonisolated|caller_isolation_inheriting}} await asyncNormalGlobalActorAcceptsSendableAsyncClosure { } }