Skip to content

Commit eb7b765

Browse files
authored
Merge pull request #79651 from xedin/sendability-checks-for-function-conversions
[Concurrency] Implement sendability checking for `@Sendable` function…
2 parents ce8458e + 156a43d commit eb7b765

File tree

3 files changed

+231
-4
lines changed

3 files changed

+231
-4
lines changed

include/swift/AST/DiagnosticsSema.def

+7
Original file line numberDiff line numberDiff line change
@@ -8363,6 +8363,13 @@ ERROR(attr_execution_type_attr_incompatible_with_isolated_any,none,
83638363
"cannot use '@execution' together with @isolated(any)",
83648364
())
83658365

8366+
ERROR(invalid_function_conversion_with_non_sendable,none,
8367+
"cannot convert %0 to %1 because crossing of an isolation boundary "
8368+
"requires parameter and result types to conform to 'Sendable' protocol",
8369+
(Type, Type))
8370+
NOTE(type_does_not_conform_to_Sendable,none,
8371+
"type %0 does not conform to 'Sendable' protocol", (Type))
8372+
83668373

83678374
#define UNDEFINE_DIAGNOSTIC_MACROS
83688375
#include "DefineDiagnosticMacros.h"

lib/Sema/TypeCheckConcurrency.cpp

+158-4
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,115 @@ 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+
// nonisolated -> @execution(caller) doesn't cross
2739+
// an isolation boundary.
2740+
if (!fromFnType->isAsync())
2741+
break;
2742+
2743+
// @execution(concurrent) -> @execution(caller)
2744+
// crosses an isolation boundary.
2745+
LLVM_FALLTHROUGH;
2746+
}
2747+
case FunctionTypeIsolation::Kind::GlobalActor:
2748+
case FunctionTypeIsolation::Kind::Erased:
2749+
diagnoseNonSendableParametersAndResult(toFnType);
2750+
break;
2751+
2752+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2753+
case FunctionTypeIsolation::Kind::Parameter:
2754+
llvm_unreachable("invalid conversion");
2755+
}
2756+
break;
2757+
}
2758+
2759+
// Converting to nonisolated synchronous or @execution(concurrent)
2760+
// asynchronous function type could require crossing an isolation
2761+
// boundary.
2762+
case FunctionTypeIsolation::Kind::NonIsolated: {
2763+
switch (fromIsolation.getKind()) {
2764+
case FunctionTypeIsolation::Kind::Parameter:
2765+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2766+
case FunctionTypeIsolation::Kind::Erased:
2767+
diagnoseNonSendableParametersAndResult(
2768+
toFnType, /*downgradeToWarning=*/true);
2769+
break;
2770+
2771+
case FunctionTypeIsolation::Kind::GlobalActor: {
2772+
// Handled above by `safeToDropGlobalActor` check.
2773+
break;
26752774
}
2775+
2776+
case FunctionTypeIsolation::Kind::NonIsolated: {
2777+
// nonisolated synchronous <-> @execution(concurrent)
2778+
if (fromFnType->isAsync() != toFnType->isAsync()) {
2779+
diagnoseNonSendableParametersAndResult(
2780+
toFnType, /*downgradeToWarning=*/true);
2781+
}
2782+
break;
2783+
}
2784+
}
2785+
break;
2786+
}
2787+
2788+
// Converting to an actor-isolated function always
2789+
// requires crossing an isolation boundary.
2790+
case FunctionTypeIsolation::Kind::GlobalActor: {
2791+
switch (fromIsolation.getKind()) {
2792+
case FunctionTypeIsolation::Kind::Parameter:
2793+
case FunctionTypeIsolation::Kind::Erased:
2794+
diagnoseNonSendableParametersAndResult(
2795+
toFnType, /*downgradeToWarning=*/true);
2796+
break;
2797+
2798+
case FunctionTypeIsolation::Kind::NonIsolated: {
2799+
// Since @execution(concurrent) as an asynchronous
2800+
// function it would mean that without Sendable
2801+
// check it would be possible for non-Sendable state
2802+
// to escape from actor isolation.
2803+
if (fromFnType->isAsync()) {
2804+
diagnoseNonSendableParametersAndResult(
2805+
toFnType, /*downgradeToWarning=*/true);
2806+
break;
2807+
}
2808+
// Runs on the actor.
2809+
break;
2810+
}
2811+
2812+
// Runs on the actor.
2813+
case FunctionTypeIsolation::Kind::NonIsolatedCaller:
2814+
break;
2815+
2816+
case FunctionTypeIsolation::Kind::GlobalActor:
2817+
llvm_unreachable("invalid conversion");
2818+
}
2819+
break;
2820+
}
2821+
2822+
// Converting to @isolated(any) doesn't cross an isolation
2823+
// boundary.
2824+
case FunctionTypeIsolation::Kind::Erased:
2825+
break;
2826+
2827+
// TODO: Figure out what exactly needs to happen here.
2828+
case FunctionTypeIsolation::Kind::Parameter:
2829+
break;
26762830
}
26772831
}
26782832
}

test/Concurrency/attr_execution_conversions.swift

+66
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
// REQUIRES: concurrency
55
// REQUIRES: swift_feature_ExecutionAttribute
66

7+
@globalActor
8+
actor MyActor {
9+
static let shared = MyActor()
10+
}
11+
712
@execution(concurrent)
813
func concurrentTest() async {
914
}
@@ -77,3 +82,64 @@ do {
7782
// expected-error@-1 {{cannot convert value of type '(@execution(caller) () async -> ()).Type' to expected argument type '(@isolated(any) () async -> Void).Type'}}
7883
}
7984

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

0 commit comments

Comments
 (0)