mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Concurrency] Diagnose loss of global actor isolation in async function conversions
Perform `Sendable` checking on parameter/result of the function type when conversion between asynchroneous functions results in a loss of global actor isolation attribute because access would result in data crossing an isolation boundary. This is a warning until Swift language mode 6. Resolves: rdar://130168104
This commit is contained in:
@@ -2274,12 +2274,6 @@ static bool safeToDropGlobalActor(
|
||||
if (otherIsolation.isErased())
|
||||
return true;
|
||||
|
||||
// We currently allow unconditional dropping of global actors from
|
||||
// async function types, despite this confusing Sendable checking
|
||||
// in light of SE-338.
|
||||
if (funcTy->isAsync())
|
||||
return true;
|
||||
|
||||
// If the argument is passed over an isolation boundary, it's not
|
||||
// safe to erase actor isolation, because the callee can call the
|
||||
// function synchronously from outside the isolation domain.
|
||||
@@ -2629,7 +2623,8 @@ namespace {
|
||||
/// be correct AND the solver doesn't know, so we must emit a diagnostic.
|
||||
void checkFunctionConversion(Expr *funcConv, Type fromType, Type toType) {
|
||||
auto diagnoseNonSendableParametersAndResult =
|
||||
[&](FunctionType *fnType, bool downgradeToWarning = false) {
|
||||
[&](FunctionType *fnType,
|
||||
std::optional<unsigned> warnUntilSwiftMode = std::nullopt) {
|
||||
auto *dc = getDeclContext();
|
||||
llvm::SmallPtrSet<Type, 2> nonSendableTypes;
|
||||
|
||||
@@ -2659,8 +2654,8 @@ namespace {
|
||||
diag::invalid_function_conversion_with_non_sendable,
|
||||
fromType, toType);
|
||||
|
||||
if (downgradeToWarning)
|
||||
diag.warnUntilFutureSwiftVersion();
|
||||
if (warnUntilSwiftMode)
|
||||
diag.warnUntilSwiftVersion(*warnUntilSwiftMode);
|
||||
}
|
||||
|
||||
for (auto type : nonSendableTypes) {
|
||||
@@ -2677,20 +2672,27 @@ namespace {
|
||||
auto toIsolation = toFnType->getIsolation();
|
||||
|
||||
if (auto fromActor = fromFnType->getGlobalActor()) {
|
||||
if (toFnType->hasGlobalActor() ||
|
||||
(toFnType->isAsync() && !toIsolation.isNonIsolatedCaller()))
|
||||
return;
|
||||
if (!toFnType->hasGlobalActor()) {
|
||||
auto dc = const_cast<DeclContext *>(getDeclContext());
|
||||
// If it's unsafe to drop global actor attribute:
|
||||
// - for Sendable types we are going to perform Sendability
|
||||
// checking of parameters/result.
|
||||
// - for non-Sendable types we either leave it to region-based
|
||||
// isolation to determine whether it's okay or not or
|
||||
// diagnose if types are not-async.
|
||||
if (safeToDropGlobalActor(dc, fromActor, toType,
|
||||
getImmediateApply())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto dc = const_cast<DeclContext*>(getDeclContext());
|
||||
if (!safeToDropGlobalActor(dc, fromActor, toType,
|
||||
getImmediateApply())) {
|
||||
// otherwise, it's not a safe cast.
|
||||
ctx.Diags
|
||||
.diagnose(funcConv->getLoc(),
|
||||
diag::converting_func_loses_global_actor, fromType,
|
||||
toType, fromActor)
|
||||
.warnUntilSwiftVersion(6);
|
||||
return;
|
||||
if (!toFnType->isAsync()) {
|
||||
ctx.Diags
|
||||
.diagnose(funcConv->getLoc(),
|
||||
diag::converting_func_loses_global_actor,
|
||||
fromType, toType, fromActor)
|
||||
.warnUntilSwiftVersion(6);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2756,11 +2758,12 @@ namespace {
|
||||
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
|
||||
case FunctionTypeIsolation::Kind::Erased:
|
||||
diagnoseNonSendableParametersAndResult(
|
||||
toFnType, /*downgradeToWarning=*/true);
|
||||
toFnType, version::Version::getFutureMajorLanguageVersion());
|
||||
break;
|
||||
|
||||
case FunctionTypeIsolation::Kind::GlobalActor: {
|
||||
// Handled above by `safeToDropGlobalActor` check.
|
||||
diagnoseNonSendableParametersAndResult(toFnType,
|
||||
/*warnUntilSwiftMode*/ 6);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2768,7 +2771,8 @@ namespace {
|
||||
// nonisolated synchronous <-> @concurrent
|
||||
if (fromFnType->isAsync() != toFnType->isAsync()) {
|
||||
diagnoseNonSendableParametersAndResult(
|
||||
toFnType, /*downgradeToWarning=*/true);
|
||||
toFnType,
|
||||
version::Version::getFutureMajorLanguageVersion());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2783,7 +2787,7 @@ namespace {
|
||||
case FunctionTypeIsolation::Kind::Parameter:
|
||||
case FunctionTypeIsolation::Kind::Erased:
|
||||
diagnoseNonSendableParametersAndResult(
|
||||
toFnType, /*downgradeToWarning=*/true);
|
||||
toFnType, version::Version::getFutureMajorLanguageVersion());
|
||||
break;
|
||||
|
||||
case FunctionTypeIsolation::Kind::NonIsolated: {
|
||||
@@ -2793,7 +2797,8 @@ namespace {
|
||||
// actor isolation.
|
||||
if (fromFnType->isAsync()) {
|
||||
diagnoseNonSendableParametersAndResult(
|
||||
toFnType, /*downgradeToWarning=*/true);
|
||||
toFnType,
|
||||
version::Version::getFutureMajorLanguageVersion());
|
||||
break;
|
||||
}
|
||||
// Runs on the actor.
|
||||
@@ -2805,7 +2810,9 @@ namespace {
|
||||
break;
|
||||
|
||||
case FunctionTypeIsolation::Kind::GlobalActor:
|
||||
llvm_unreachable("invalid conversion");
|
||||
diagnoseNonSendableParametersAndResult(
|
||||
toFnType, version::Version::getFutureMajorLanguageVersion());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,11 @@ func testNonSendableDiagnostics(
|
||||
let _: nonisolated(nonsending) () async -> NonSendable = globalActor2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
// expected-error@-1 {{cannot convert '@MainActor @Sendable () async -> NonSendable' to 'nonisolated(nonsending) () async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
|
||||
|
||||
let _: @concurrent (NonSendable) async -> Void = globalActor1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
// expected-warning@-1 {{cannot convert '@MainActor @Sendable (NonSendable) async -> Void' to '(NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
|
||||
let _: @concurrent () async -> NonSendable = globalActor2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
// expected-warning@-1 {{cannot convert '@MainActor @Sendable () async -> NonSendable' to '() async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
|
||||
|
||||
let _: nonisolated(nonsending) (NonSendable) async -> Void = erased1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
// expected-error@-1 {{cannot convert '@isolated(any) @Sendable (NonSendable) async -> Void' to 'nonisolated(nonsending) (NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
|
||||
let _: nonisolated(nonsending) () async -> NonSendable = erased2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
|
||||
@@ -396,8 +396,8 @@ func globalActorConversions3(_ x: @escaping @concurrent (SendableKlass) async ->
|
||||
_ = await v5(SendableKlass())
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s21attr_execution_silgen26conversionsFromSyncToAsyncyyyAA16NonSendableKlassCYbc_yAA0jK0CYbScMYccyADYbScMYcctYaF : $@convention(thin) @async (@guaranteed @Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> (), @guaranteed @Sendable @callee_guaranteed (@guaranteed SendableKlass) -> (), @guaranteed @Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> ()) -> () {
|
||||
// CHECK: bb0([[X:%.*]] : @guaranteed $@Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> (), [[Y:%.*]] : @guaranteed $@Sendable @callee_guaranteed (@guaranteed SendableKlass) -> (), [[Z:%.*]] : @guaranteed $@Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> ()):
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s21attr_execution_silgen26conversionsFromSyncToAsyncyyyAA16NonSendableKlassCYbc_yAA0jK0CYbScMYcctYaF : $@convention(thin) @async (@guaranteed @Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> (), @guaranteed @Sendable @callee_guaranteed (@guaranteed SendableKlass) -> ()) -> ()
|
||||
// CHECK: bb0([[X:%.*]] : @guaranteed $@Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> (), [[Y:%.*]] : @guaranteed $@Sendable @callee_guaranteed (@guaranteed SendableKlass) -> ()):
|
||||
|
||||
// CHECK: [[X_C:%.*]] = copy_value [[X]]
|
||||
// CHECK: [[THUNK:%.*]] = function_ref @$s21attr_execution_silgen16NonSendableKlassCIeghg_ScA_pSgACIegHgg_TR : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>, @guaranteed NonSendableKlass, @guaranteed @Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> ()) -> ()
|
||||
@@ -411,19 +411,13 @@ func globalActorConversions3(_ x: @escaping @concurrent (SendableKlass) async ->
|
||||
// CHECK: [[THUNK:%.*]] = function_ref @$s21attr_execution_silgen13SendableKlassCIeghg_ACIegHg_TRScMTU : $@convention(thin) @async (@guaranteed SendableKlass, @guaranteed @Sendable @callee_guaranteed (@guaranteed SendableKlass) -> ()) -> ()
|
||||
// CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[THUNK]]([[Y_C]])
|
||||
|
||||
// CHECK: [[Z_C:%.*]] = copy_value [[Z]]
|
||||
// CHECK: [[THUNK:%.*]] = function_ref @$s21attr_execution_silgen16NonSendableKlassCIeghg_ACIegHg_TRScMTU : $@convention(thin) @async (@guaranteed NonSendableKlass, @guaranteed @Sendable @callee_guaranteed (@guaranteed NonSendableKlass) -> ()) -> ()
|
||||
// CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[THUNK]]([[Z_C]])
|
||||
|
||||
// CHECK: } // end sil function '$s21attr_execution_silgen26conversionsFromSyncToAsyncyyyAA16NonSendableKlassCYbc_yAA0jK0CYbScMYccyADYbScMYcctYaF'
|
||||
// CHECK: } // end sil function '$s21attr_execution_silgen26conversionsFromSyncToAsyncyyyAA16NonSendableKlassCYbc_yAA0jK0CYbScMYcctYaF'
|
||||
|
||||
func conversionsFromSyncToAsync(_ x: @escaping @Sendable (NonSendableKlass) -> Void,
|
||||
_ y: @escaping @MainActor @Sendable (SendableKlass) -> Void,
|
||||
_ z: @escaping @MainActor @Sendable (NonSendableKlass) -> Void) async {
|
||||
_ y: @escaping @MainActor @Sendable (SendableKlass) -> Void) async {
|
||||
let _: nonisolated(nonsending) (NonSendableKlass) async -> Void = x
|
||||
let _: nonisolated(nonsending) (SendableKlass) async -> Void = y
|
||||
let _: @concurrent (SendableKlass) async -> Void = y
|
||||
let _: @concurrent (NonSendableKlass) async -> Void = z
|
||||
}
|
||||
|
||||
func testThatClosuresAssumeIsolation(fn: inout nonisolated(nonsending) (Int) async -> Void) {
|
||||
|
||||
15
test/Concurrency/global_actor_function_types_swift6.swift
Normal file
15
test/Concurrency/global_actor_function_types_swift6.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple -language-mode 6
|
||||
|
||||
final class NonSendable {
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
extension NonSendable: Sendable {}
|
||||
|
||||
actor Test {
|
||||
func testNonSendableCrossingIsolationinAsync(v: NonSendable) {
|
||||
let _: () async -> NonSendable = { @MainActor in v }
|
||||
// expected-error@-1 {{cannot convert '@MainActor @Sendable () async -> NonSendable' to '() async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
|
||||
// expected-note@-2 {{type 'NonSendable' does not conform to 'Sendable' protocol}}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user