Skip to content

Commit cb584d8

Browse files
committed
[Concurrency] Use the new "call" path for subscript isolation checking
1 parent 1259125 commit cb584d8

File tree

4 files changed

+139
-75
lines changed

4 files changed

+139
-75
lines changed

include/swift/AST/DiagnosticsSema.def

+3
Original file line numberDiff line numberDiff line change
@@ -5077,6 +5077,9 @@ ERROR(actor_isolated_non_self_reference,none,
50775077
"from a non-isolated autoclosure}3",
50785078
(DescriptiveDeclKind, DeclName, unsigned, unsigned, Type,
50795079
ActorIsolation))
5080+
ERROR(subscript_mutation_outside_actor,none,
5081+
"%0 %1 %2 cannot be mutated from outside the actor",
5082+
(ActorIsolation, DescriptiveDeclKind, DeclName))
50805083
ERROR(distributed_actor_isolated_non_self_reference,none,
50815084
"distributed actor-isolated %0 %1 can not be accessed from a "
50825085
"non-isolated context",

lib/Sema/TypeCheckConcurrency.cpp

+121-71
Original file line numberDiff line numberDiff line change
@@ -2041,48 +2041,41 @@ namespace {
20412041
contextStack.push_back(dc);
20422042
}
20432043

2044-
/// Searches the applyStack from back to front for the inner-most CallExpr
2045-
/// and marks that CallExpr as implicitly async.
2046-
///
2047-
/// NOTE: Crashes if no CallExpr was found.
2048-
///
2049-
/// For example, for global actor function `curryAdd`, if we have:
2050-
/// ((curryAdd 1) 2)
2051-
/// then we want to mark the inner-most CallExpr, `(curryAdd 1)`.
2052-
///
2053-
/// The same goes for calls to member functions, such as calc.add(1, 2),
2054-
/// aka ((add calc) 1 2), looks like this:
2055-
///
2056-
/// (call_expr
2057-
/// (dot_syntax_call_expr
2058-
/// (declref_expr add)
2059-
/// (declref_expr calc))
2060-
/// (tuple_expr
2061-
/// ...))
2062-
///
2063-
/// and we reach up to mark the CallExpr.
2064-
void markNearestCallAsImplicitly(llvm::Optional<ActorIsolation> setAsync,
2065-
bool setThrows = false,
2066-
bool setDistributedThunk = false) {
2067-
assert(applyStack.size() > 0 && "not contained within an Apply?");
2068-
2069-
const auto End = applyStack.rend();
2070-
for (auto I = applyStack.rbegin(); I != End; ++I)
2071-
if (auto call = dyn_cast<CallExpr>(*I)) {
2072-
if (setAsync) {
2073-
call->setImplicitlyAsync(*setAsync);
2074-
}
2075-
if (setThrows) {
2076-
call->setImplicitlyThrows(true);
2077-
}else {
2078-
call->setImplicitlyThrows(false);
2079-
}
2080-
if (setDistributedThunk) {
2081-
call->setShouldApplyDistributedThunk(true);
2082-
}
2083-
return;
2044+
/// Mark the given expression
2045+
void markImplicitlyPromoted(
2046+
Expr *expr,
2047+
Optional<ActorIsolation> setAsync,
2048+
bool setThrows = false,
2049+
bool setDistributedThunk = false) {
2050+
if (auto apply = dyn_cast<ApplyExpr>(expr)) {
2051+
if (setAsync) {
2052+
apply->setImplicitlyAsync(*setAsync);
2053+
}
2054+
2055+
apply->setImplicitlyThrows(setThrows);
2056+
if (setDistributedThunk) {
2057+
apply->setShouldApplyDistributedThunk(true);
2058+
}
2059+
2060+
return;
2061+
}
2062+
2063+
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2064+
assert(usageEnv(lookup) == VarRefUseEnv::Read);
2065+
2066+
if (setAsync)
2067+
lookup->setImplicitlyAsync(*setAsync);
2068+
lookup->setImplicitlyThrows(setThrows);
2069+
2070+
if (setDistributedThunk) {
2071+
if (auto memberRef = dyn_cast<MemberRefExpr>(lookup))
2072+
memberRef->setAccessViaDistributedThunk();
20842073
}
2085-
llvm_unreachable("expected a CallExpr in applyStack!");
2074+
2075+
return;
2076+
}
2077+
2078+
llvm_unreachable("Node cannot be implicitly promoted");
20862079
}
20872080

20882081
bool shouldWalkCaptureInitializerExpressions() override { return true; }
@@ -2153,6 +2146,19 @@ namespace {
21532146
if (auto load = dyn_cast<LoadExpr>(expr))
21542147
recordMutableVarParent(load, load->getSubExpr());
21552148

2149+
if (auto subscript = dyn_cast<SubscriptExpr>(expr)) {
2150+
if (ConcreteDeclRef subscriptDecl = subscript->getMember()) {
2151+
Type type = subscriptDecl.getDecl()->getInterfaceType().subst(
2152+
subscriptDecl.getSubstitutions());
2153+
if (auto fnType = type->getAs<FunctionType>()) {
2154+
(void)checkApply(
2155+
subscript, subscriptDecl, subscript->getBase(),
2156+
fnType, subscript->getArgs());
2157+
}
2158+
}
2159+
return Action::Continue(expr);
2160+
}
2161+
21562162
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
21572163
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
21582164
/*partialApply*/ llvm::None, lookup);
@@ -2207,7 +2213,14 @@ namespace {
22072213
}
22082214
} else {
22092215
// Check the call itself.
2210-
(void)checkApply(apply);
2216+
if (auto fnExprType = getType(apply->getFn())) {
2217+
if (auto fnType = fnExprType->getAs<FunctionType>()) {
2218+
(void)checkApply(
2219+
apply,
2220+
apply->getCalledValue(/*skipFunctionConversions=*/true),
2221+
apply->getFn(), fnType, apply->getArgs());
2222+
}
2223+
}
22112224
}
22122225
}
22132226

@@ -2591,8 +2604,6 @@ namespace {
25912604
}
25922605
}
25932606

2594-
// FIXME: Subscript?
2595-
25962607
// This is either non-distributed variable, subscript, or something else.
25972608
ctx.Diags.diagnose(declLoc,
25982609
diag::distributed_actor_isolated_non_self_reference,
@@ -2697,14 +2708,11 @@ namespace {
26972708
}
26982709

26992710
/// Check actor isolation for a particular application.
2700-
bool checkApply(ApplyExpr *apply) {
2701-
auto fnExprType = getType(apply->getFn());
2702-
if (!fnExprType)
2703-
return false;
2704-
2705-
auto fnType = fnExprType->getAs<FunctionType>();
2706-
if (!fnType)
2707-
return false;
2711+
bool checkApply(
2712+
Expr *apply, ConcreteDeclRef callee, Expr *base,
2713+
AnyFunctionType *fnType, ArgumentList *args
2714+
) {
2715+
SourceLoc loc = apply->getLoc();
27082716

27092717
// The isolation of the context we're in.
27102718
llvm::Optional<ActorIsolation> contextIsolation;
@@ -2718,17 +2726,22 @@ namespace {
27182726
return *contextIsolation;
27192727
};
27202728

2729+
// Determine whether the calle itself is async.
2730+
auto calleeDecl = callee.getDecl();
2731+
bool isCalleeAsync = calleeDecl
2732+
? isAsyncDecl(calleeDecl)
2733+
: fnType->getExtInfo().isAsync();
2734+
27212735
// Default the call options to allow promotion to async, if it will be
27222736
// warranted.
27232737
ActorReferenceResult::Options callOptions;
2724-
if (!fnType->getExtInfo().isAsync())
2738+
if (!isCalleeAsync)
27252739
callOptions |= ActorReferenceResult::Flags::AsyncPromotion;
27262740

27272741
// Determine from the callee whether actor isolation is unsatisfied.
27282742
llvm::Optional<ActorIsolation> unsatisfiedIsolation;
27292743
bool mayExitToNonisolated = true;
27302744
Expr *argForIsolatedParam = nullptr;
2731-
auto calleeDecl = apply->getCalledValue(/*skipFunctionConversions=*/true);
27322745
if (Type globalActor = fnType->getGlobalActor()) {
27332746
// If the function type is global-actor-qualified, determine whether
27342747
// we are within that global actor already.
@@ -2739,8 +2752,35 @@ namespace {
27392752
}
27402753

27412754
mayExitToNonisolated = false;
2755+
} else if (calleeDecl &&
2756+
calleeDecl->getAttrs()
2757+
.hasAttribute<UnsafeInheritExecutorAttr>()) {
2758+
// The callee always inherits the executor.
2759+
return false;
2760+
} else if (calleeDecl && isa<SubscriptDecl>(calleeDecl)) {
2761+
auto isolatedActor = getIsolatedActor(base);
2762+
auto result = ActorReferenceResult::forReference(
2763+
callee, base->getLoc(), getDeclContext(),
2764+
kindOfUsage(calleeDecl, apply),
2765+
isolatedActor, None, None, getClosureActorIsolation);
2766+
switch (result) {
2767+
case ActorReferenceResult::SameConcurrencyDomain:
2768+
break;
2769+
2770+
case ActorReferenceResult::ExitsActorToNonisolated:
2771+
unsatisfiedIsolation = ActorIsolation::forIndependent();
2772+
break;
2773+
2774+
case ActorReferenceResult::EntersActor:
2775+
unsatisfiedIsolation = result.isolation;
2776+
break;
2777+
}
2778+
2779+
callOptions = result.options;
2780+
mayExitToNonisolated = false;
2781+
argForIsolatedParam = base;
27422782
} else if (auto *selfApplyFn = dyn_cast<SelfApplyExpr>(
2743-
apply->getFn()->getValueProvidingExpr())) {
2783+
base->getValueProvidingExpr())) {
27442784
// If we're calling a member function, check whether the function
27452785
// itself is isolated.
27462786
auto memberFn = selfApplyFn->getFn()->getValueProvidingExpr();
@@ -2768,10 +2808,6 @@ namespace {
27682808
calleeDecl = memberRef->first.getDecl();
27692809
argForIsolatedParam = selfApplyFn->getBase();
27702810
}
2771-
} else if (calleeDecl &&
2772-
calleeDecl->getAttrs()
2773-
.hasAttribute<UnsafeInheritExecutorAttr>()) {
2774-
return false;
27752811
}
27762812

27772813
// Check for isolated parameters.
@@ -2780,7 +2816,6 @@ namespace {
27802816
if (!fnType->getParams()[paramIdx].isIsolated())
27812817
continue;
27822818

2783-
auto *args = apply->getArgs();
27842819
if (paramIdx >= args->size())
27852820
continue;
27862821

@@ -2802,7 +2837,7 @@ namespace {
28022837
unsatisfiedIsolation =
28032838
ActorIsolation::forActorInstanceParameter(nominal, paramIdx);
28042839

2805-
if (!fnType->getExtInfo().isAsync())
2840+
if (!isCalleeAsync)
28062841
callOptions |= ActorReferenceResult::Flags::AsyncPromotion;
28072842
mayExitToNonisolated = false;
28082843

@@ -2811,7 +2846,7 @@ namespace {
28112846

28122847
// If we're calling an async function that's nonisolated, and we're in
28132848
// an isolated context, then we're exiting the actor context.
2814-
if (mayExitToNonisolated && fnType->isAsync() &&
2849+
if (mayExitToNonisolated && isCalleeAsync &&
28152850
getContextIsolation().isActorIsolated())
28162851
unsatisfiedIsolation = ActorIsolation::forIndependent();
28172852

@@ -2827,7 +2862,7 @@ namespace {
28272862
if (requiresAsync && !getDeclContext()->isAsyncContext()) {
28282863
if (calleeDecl) {
28292864
ctx.Diags.diagnose(
2830-
apply->getLoc(), diag::actor_isolated_call_decl,
2865+
loc, diag::actor_isolated_call_decl,
28312866
*unsatisfiedIsolation,
28322867
calleeDecl->getDescriptiveKind(), calleeDecl->getName(),
28332868
getContextIsolation())
@@ -2837,7 +2872,7 @@ namespace {
28372872
calleeDecl->getName());
28382873
} else {
28392874
ctx.Diags.diagnose(
2840-
apply->getLoc(), diag::actor_isolated_call, *unsatisfiedIsolation,
2875+
loc, diag::actor_isolated_call, *unsatisfiedIsolation,
28412876
getContextIsolation())
28422877
.warnUntilSwiftVersionIf(getContextIsolation().preconcurrency(), 6);
28432878
}
@@ -2859,7 +2894,7 @@ namespace {
28592894
if (unsatisfiedIsolation->isDistributedActor() &&
28602895
!(calleeDecl && isa<ConstructorDecl>(calleeDecl))) {
28612896
auto distributedAccess = checkDistributedAccess(
2862-
apply->getFn()->getLoc(), calleeDecl, argForIsolatedParam);
2897+
loc, calleeDecl, argForIsolatedParam);
28632898
if (!distributedAccess)
28642899
return true;
28652900

@@ -2868,8 +2903,23 @@ namespace {
28682903

28692904
// Mark as implicitly async/throws/distributed thunk as needed.
28702905
if (requiresAsync || setThrows || usesDistributedThunk) {
2871-
markNearestCallAsImplicitly(
2872-
unsatisfiedIsolation, setThrows, usesDistributedThunk);
2906+
// For a subscript, make sure it's a read-only access.
2907+
if (calleeDecl && isa<SubscriptDecl>(calleeDecl)) {
2908+
auto useKind = usageEnv(cast<LookupExpr>(apply));
2909+
if (useKind != VarRefUseEnv::Read) {
2910+
ctx.Diags.diagnose(loc, diag::subscript_mutation_outside_actor,
2911+
*unsatisfiedIsolation,
2912+
calleeDecl->getDescriptiveKind(),
2913+
calleeDecl->getName());
2914+
calleeDecl->diagnose(
2915+
diag::actor_mutable_state, calleeDecl->getDescriptiveKind());
2916+
2917+
return true;
2918+
}
2919+
}
2920+
2921+
markImplicitlyPromoted(
2922+
apply, unsatisfiedIsolation, setThrows, usesDistributedThunk);
28732923
}
28742924

28752925
// Check for sendability of the parameter types.
@@ -2878,9 +2928,9 @@ namespace {
28782928
const auto &param = params[paramIdx];
28792929

28802930
// Dig out the location of the argument.
2881-
SourceLoc argLoc = apply->getLoc();
2931+
SourceLoc argLoc = loc;
28822932
Type argType;
2883-
if (auto argList = apply->getArgs()) {
2933+
if (auto argList = args) {
28842934
auto arg = argList->get(paramIdx);
28852935
if (arg.getStartLoc().isValid())
28862936
argLoc = arg.getStartLoc();
@@ -2904,9 +2954,9 @@ namespace {
29042954

29052955
// Check for sendability of the result type.
29062956
if (diagnoseNonSendableTypes(
2907-
fnType->getResult(), getDeclContext(), apply->getLoc(),
2957+
fnType->getResult(), getDeclContext(), loc,
29082958
diag::non_sendable_call_result_type,
2909-
apply->isImplicitlyAsync().has_value(),
2959+
requiresAsync,
29102960
*unsatisfiedIsolation))
29112961
return true;
29122962

test/Concurrency/global_actor_from_ordinary_context.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func referenceGlobalActor() async {
3737

3838
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
3939
_ = a[1] // expected-note{{subscript access is 'async'}}
40-
a[0] = 1 // expected-error{{global actor 'SomeGlobalActor'-isolated subscript 'subscript(_:)' can not be mutated from a non-isolated context}}
40+
a[0] = 1 // expected-error{{global actor 'SomeGlobalActor'-isolated subscript 'subscript(_:)' cannot be mutated from outside the actor}}
4141

4242
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
4343
_ = 32 + a[1] // expected-note@:12{{subscript access is 'async'}}
@@ -51,10 +51,12 @@ func referenceGlobalActor2() {
5151
}
5252

5353

54+
// expected-note@+2 {{add '@SomeGlobalActor' to make global function 'referenceAsyncGlobalActor()' part of global actor 'SomeGlobalActor'}}
5455
// expected-note@+1 {{add 'async' to function 'referenceAsyncGlobalActor()' to make it asynchronous}} {{33-33= async}}
5556
func referenceAsyncGlobalActor() {
56-
let y = asyncGlobalActFn
57+
let y = asyncGlobalActFn // expected-note{{calls to let 'y' from outside of its actor context are implicitly asynchronous}}
5758
y() // expected-error{{'async' call in a function that does not support concurrency}}
59+
// expected-error@-1{{call to global actor 'SomeGlobalActor'-isolated let 'y' in a synchronous nonisolated context}}
5860
}
5961

6062

@@ -111,7 +113,7 @@ func fromAsync() async {
111113

112114
let y = asyncGlobalActFn
113115
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
114-
y() // expected-note{{call is 'async'}}
116+
y() // expected-note{{calls to let 'y' from outside of its actor context are implicitly asynchronous}}
115117

116118
let a = Alex()
117119
let fn = a.method
@@ -124,7 +126,7 @@ func fromAsync() async {
124126
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
125127
_ = a[1] // expected-note{{subscript access is 'async'}}
126128
_ = await a[1]
127-
a[0] = 1 // expected-error{{global actor 'SomeGlobalActor'-isolated subscript 'subscript(_:)' can not be mutated from a non-isolated context}}
129+
a[0] = 1 // expected-error{{global actor 'SomeGlobalActor'-isolated subscript 'subscript(_:)' cannot be mutated from outside the actor}}
128130
}
129131

130132
// expected-note@+1{{mutation of this var is only permitted within the actor}}

test/Concurrency/sendable_checking.swift

+9
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,19 @@ class SubWUnsafeSubscript : SuperWUnsafeSubscript {
223223
extension MyActor {
224224
func f(_: Any) { }
225225
func g(_: () -> Void) { }
226+
227+
subscript (value: Any) -> String { "\(value)" }
228+
subscript (value: () -> Void) -> String {
229+
value()
230+
return "hello"
231+
}
226232
}
227233

228234
@available(SwiftStdlib 5.1, *)
229235
func testConversionsAndSendable(a: MyActor, s: any Sendable, f: @Sendable () -> Void) async {
230236
await a.f(s)
231237
await a.g(f)
238+
239+
_ = await a[s]
240+
_ = await a[f]
232241
}

0 commit comments

Comments
 (0)