Skip to content

Commit 45ca72e

Browse files
committed
[Concurrency] Implement sendability checking for @Sendable function type conversions
Function conversions that cross an isolation boundary require `Sendable` argument and result types, and the destination function type must be `async`.
1 parent 7c258f2 commit 45ca72e

File tree

3 files changed

+205
-4
lines changed

3 files changed

+205
-4
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8323,6 +8323,13 @@ ERROR(attr_execution_type_attr_incompatible_with_isolated_any,none,
83238323
"cannot use '@execution' together with @isolated(any)",
83248324
())
83258325

8326+
ERROR(invalid_function_conversion_with_non_sendable,none,
8327+
"cannot convert %0 to %1 because crossing of an isolation boundary "
8328+
"requires parameter and result types to conform to 'Sendable' protocol",
8329+
(Type, Type))
8330+
NOTE(type_does_not_conform_to_Sendable,none,
8331+
"type %0 does not conform to 'Sendable' protocol", (Type))
8332+
83268333

83278334
#define UNDEFINE_DIAGNOSTIC_MACROS
83288335
#include "DefineDiagnosticMacros.h"

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,12 +2654,58 @@ namespace {
26542654
/// Some function conversions synthesized by the constraint solver may not
26552655
/// be correct AND the solver doesn't know, so we must emit a diagnostic.
26562656
void checkFunctionConversion(Expr *funcConv, Type fromType, Type toType) {
2657+
auto diagnoseNonSendableParametersAndResult =
2658+
[&](FunctionType *fnType, bool downgradeToWarning = false) {
2659+
auto *dc = getDeclContext();
2660+
llvm::SmallPtrSet<Type, 2> nonSendableTypes;
2661+
2662+
SendableCheckContext context(dc);
2663+
for (auto &param : fnType->getParams()) {
2664+
diagnoseNonSendableTypes(
2665+
param.getPlainType(), context,
2666+
/*inDerivedConformance=*/Type(), funcConv->getLoc(),
2667+
[&](Type type, DiagnosticBehavior behavior) {
2668+
nonSendableTypes.insert(type);
2669+
return true;
2670+
});
2671+
}
2672+
2673+
diagnoseNonSendableTypes(
2674+
fnType->getResult(), context,
2675+
/*inDerivedConformance=*/Type(), funcConv->getLoc(),
2676+
[&](Type type, DiagnosticBehavior behavior) {
2677+
nonSendableTypes.insert(type);
2678+
return true;
2679+
});
2680+
2681+
if (!nonSendableTypes.empty()) {
2682+
auto &ctx = dc->getASTContext();
2683+
{
2684+
auto diag = ctx.Diags.diagnose(
2685+
funcConv->getLoc(),
2686+
diag::invalid_function_conversion_with_non_sendable,
2687+
fromType, toType);
2688+
2689+
if (downgradeToWarning)
2690+
diag.warnUntilSwiftVersion(7);
2691+
}
2692+
2693+
for (auto type : nonSendableTypes) {
2694+
ctx.Diags.diagnose(funcConv->getLoc(),
2695+
diag::type_does_not_conform_to_Sendable,
2696+
type);
2697+
}
2698+
}
2699+
};
2700+
26572701
if (auto fromFnType = fromType->getAs<FunctionType>()) {
2658-
if (auto fromActor = fromFnType->getGlobalActor()) {
2659-
if (auto toFnType = toType->getAs<FunctionType>()) {
2702+
if (auto toFnType = toType->getAs<FunctionType>()) {
2703+
auto fromIsolation = fromFnType->getIsolation();
2704+
auto toIsolation = toFnType->getIsolation();
26602705

2661-
// ignore some kinds of casts, as they're diagnosed elsewhere.
2662-
if (toFnType->hasGlobalActor() || toFnType->isAsync())
2706+
if (auto fromActor = fromFnType->getGlobalActor()) {
2707+
if (toFnType->hasGlobalActor() ||
2708+
(toFnType->isAsync() && !toIsolation.isNonIsolatedCaller()))
26632709
return;
26642710

26652711
auto dc = const_cast<DeclContext*>(getDeclContext());
@@ -2672,7 +2718,99 @@ namespace {
26722718
diag::converting_func_loses_global_actor, fromType,
26732719
toType, fromActor)
26742720
.warnUntilSwiftVersion(6);
2721+
return;
2722+
}
2723+
}
2724+
2725+
// Conversions from non-Sendable types are handled by
2726+
// region-based isolation.
2727+
// Function conversions are used to inject concurrency attributes
2728+
// into interface types until that changes we won't be able to
2729+
// diagnose all of the cases here.
2730+
if (!fromFnType->isSendable())
2731+
return;
2732+
2733+
switch (toIsolation.getKind()) {
2734+
// Converting to `@execution(caller)` function type
2735+
case FunctionTypeIsolation::Kind::NonIsolatedCaller: {
2736+
switch (fromIsolation.getKind()) {
2737+
case FunctionTypeIsolation::Kind::NonIsolated:
2738+
case FunctionTypeIsolation::Kind::GlobalActor:
2739+
case FunctionTypeIsolation::Kind::Erased:
2740+
diagnoseNonSendableParametersAndResult(toFnType);
2741+
break;
2742+
2743+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2744+
case FunctionTypeIsolation::Kind::Parameter:
2745+
llvm_unreachable("invalid conversion");
2746+
}
2747+
break;
2748+
}
2749+
2750+
// Converting to nonisolated synchronous or @execution(concurrent)
2751+
// asynchronous function type could require crossing an isolation
2752+
// boundary.
2753+
case FunctionTypeIsolation::Kind::NonIsolated: {
2754+
switch (fromIsolation.getKind()) {
2755+
case FunctionTypeIsolation::Kind::Parameter:
2756+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2757+
case FunctionTypeIsolation::Kind::Erased:
2758+
diagnoseNonSendableParametersAndResult(
2759+
toFnType, /*downgradeToWarning=*/true);
2760+
break;
2761+
2762+
case FunctionTypeIsolation::Kind::GlobalActor: {
2763+
// Handled above by `safeToDropGlobalActor` check.
2764+
break;
2765+
}
2766+
2767+
case FunctionTypeIsolation::Kind::NonIsolated: {
2768+
// nonisolated synchronous <-> @execution(concurrent)
2769+
if (fromFnType->isAsync() != toFnType->isAsync()) {
2770+
diagnoseNonSendableParametersAndResult(
2771+
toFnType, /*downgradeToWarning=*/true);
2772+
}
2773+
break;
2774+
}
2775+
}
2776+
break;
2777+
}
2778+
2779+
// Converting to an actor-isolated function always
2780+
// requires crossing an isolation boundary.
2781+
case FunctionTypeIsolation::Kind::GlobalActor: {
2782+
switch (fromIsolation.getKind()) {
2783+
case FunctionTypeIsolation::Kind::Parameter:
2784+
case FunctionTypeIsolation::Kind::Erased:
2785+
diagnoseNonSendableParametersAndResult(
2786+
toFnType, /*downgradeToWarning=*/true);
2787+
break;
2788+
2789+
case FunctionTypeIsolation::Kind::GlobalActor:
2790+
// Actor isolation change.
2791+
if (fromIsolation.getGlobalActorType()->isEqual(
2792+
toIsolation.getGlobalActorType())) {
2793+
diagnoseNonSendableParametersAndResult(
2794+
toFnType, /*downgradeToWarning=*/true);
2795+
}
2796+
break;
2797+
2798+
// Runs on the actor.
2799+
case FunctionTypeIsolation::Kind::NonIsolated:
2800+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2801+
break;
26752802
}
2803+
break;
2804+
}
2805+
2806+
// Converting to @isolated(any) doesn't cross an isolation
2807+
// boundary.
2808+
case FunctionTypeIsolation::Kind::Erased:
2809+
break;
2810+
2811+
// TODO: Figure out what exactly needs to happen here.
2812+
case FunctionTypeIsolation::Kind::Parameter:
2813+
break;
26762814
}
26772815
}
26782816
}

test/Concurrency/attr_execution_conversions.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,59 @@ do {
7777
// expected-error@-1 {{cannot convert value of type '(@execution(caller) () async -> ()).Type' to expected argument type '(@isolated(any) () async -> Void).Type'}}
7878
}
7979

80+
81+
// Converting to `@execution(caller)` function
82+
class NonSendable {}
83+
84+
func testNonSendableDiagnostics(
85+
globalActor1: @escaping @Sendable @MainActor (NonSendable) async -> Void,
86+
globalActor2: @escaping @Sendable @MainActor () async -> NonSendable,
87+
erased1: @escaping @Sendable @isolated(any) (NonSendable) async -> Void,
88+
erased2: @escaping @Sendable @isolated(any) () async -> NonSendable,
89+
nonIsolated1: @escaping @Sendable (NonSendable) -> Void,
90+
nonIsolated2: @escaping @Sendable @execution(concurrent) (NonSendable) async -> Void,
91+
nonIsolated3: @escaping @Sendable () -> NonSendable,
92+
nonIsolated4: @escaping @Sendable @execution(concurrent) () async -> NonSendable,
93+
caller1: @escaping @Sendable @execution(caller) (NonSendable) async -> Void,
94+
caller2: @escaping @Sendable @execution(caller) () async -> NonSendable
95+
) {
96+
let _: @execution(caller) (NonSendable) async -> Void = globalActor1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
97+
// expected-error@-1 {{cannot convert '@MainActor @Sendable (NonSendable) async -> Void' to '@execution(caller) (NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
98+
let _: @execution(caller) () async -> NonSendable = globalActor2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
99+
// expected-error@-1 {{cannot convert '@MainActor @Sendable () async -> NonSendable' to '@execution(caller) () async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
100+
101+
let _: @execution(caller) (NonSendable) async -> Void = erased1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
102+
// expected-error@-1 {{cannot convert '@isolated(any) @Sendable (NonSendable) async -> Void' to '@execution(caller) (NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
103+
let _: @execution(caller) () async -> NonSendable = erased2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
104+
// expected-error@-1 {{cannot convert '@isolated(any) @Sendable () async -> NonSendable' to '@execution(caller) () async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
105+
106+
let _: @execution(caller) (NonSendable) async -> Void = nonIsolated1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
107+
// expected-error@-1 {{annot convert '@Sendable (NonSendable) -> Void' to '@execution(caller) (NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
108+
let _: @execution(caller) (NonSendable) async -> Void = nonIsolated2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
109+
// expected-error@-1 {{cannot convert '@Sendable (NonSendable) async -> Void' to '@execution(caller) (NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
110+
111+
let _: @execution(caller) () async -> NonSendable = nonIsolated3 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
112+
// expected-error@-1 {{cannot convert '@Sendable () -> NonSendable' to '@execution(caller) () async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
113+
let _: @execution(caller) () async -> NonSendable = nonIsolated4 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
114+
// expected-error@-1 {{cannot convert '@Sendable () async -> NonSendable' to '@execution(caller) () async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
115+
116+
let _: @execution(concurrent) (NonSendable) async -> Void = erased1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
117+
// expected-warning@-1 {{cannot convert '@isolated(any) @Sendable (NonSendable) async -> Void' to '(NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
118+
let _: @execution(concurrent) () async -> NonSendable = erased2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
119+
// expected-warning@-1 {{cannot convert '@isolated(any) @Sendable () async -> NonSendable' to '() async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
120+
121+
122+
let _: @execution(concurrent) (NonSendable) async -> Void = caller1 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
123+
// expected-warning@-1 {{cannot convert '@execution(caller) @Sendable (NonSendable) async -> Void' to '(NonSendable) async -> Void' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
124+
let _: @execution(concurrent) () async -> NonSendable = caller2 // expected-note {{type 'NonSendable' does not conform to 'Sendable' protocol}}
125+
// expected-warning@-1 {{cannot convert '@execution(caller) @Sendable () async -> NonSendable' to '() async -> NonSendable' because crossing of an isolation boundary requires parameter and result types to conform to 'Sendable' protocol}}
126+
127+
let _: @MainActor (NonSendable) async -> Void = nonIsolated1 // Ok
128+
let _: @MainActor (NonSendable) async -> Void = nonIsolated2 // Ok
129+
130+
let _: @MainActor () async -> NonSendable = nonIsolated3 // Ok
131+
let _: @MainActor () async -> NonSendable = nonIsolated4 // Ok
132+
133+
let _: @MainActor (NonSendable) async -> Void = caller1 // Ok
134+
let _: @MainActor () async -> NonSendable = caller2 // Ok
135+
}

0 commit comments

Comments
 (0)