Skip to content

Commit d78a5ed

Browse files
committed
Handle all isolation checking for function calls in one place
Isolation checking for calls had two separate implementation places: one that looked at the declaration being called (for member declarations) and one that worked on the actual call expression. Unify on the latter implementation, which is more general and has access to the specific call arguments. Improve diagnostics here somewher so we don't regress in that area. This refactoring shouldn't change the actual semantics, but it makes upcoming semantic changes easier.
1 parent 270089f commit d78a5ed

11 files changed

+139
-98
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5239,9 +5239,9 @@ WARNING(non_sendable_param_type,none,
52395239
"in parameter of superclass method overridden by %4 %2 %3|"
52405240
"in parameter of %4 '@objc' %2 %3}1 cannot cross actor boundary",
52415241
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
5242-
WARNING(non_sendable_call_param_type,none,
5243-
"non-sendable type %0 passed in %select{implicitly asynchronous |}1"
5244-
"call to %2 function cannot cross actor boundary",
5242+
WARNING(non_sendable_call_argument,none,
5243+
"passing argument of non-sendable type %0 %select{into %2 context|"
5244+
"outside of %2 context}1 may introduce data races",
52455245
(Type, bool, ActorIsolation))
52465246
WARNING(non_sendable_result_type,none,
52475247
"non-sendable type %0 returned by %select{call to %4 %2 %3|"

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2177,11 +2177,6 @@ namespace {
21772177
}
21782178

21792179
if (auto apply = dyn_cast<ApplyExpr>(expr)) {
2180-
applyStack.push_back(apply); // record this encounter
2181-
2182-
// Check the call itself.
2183-
(void)checkApply(apply);
2184-
21852180
// If this is a call to a partial apply thunk, decompose it to check it
21862181
// like based on the original written syntax, e.g., "self.method".
21872182
if (auto partialApply = decomposePartialApplyThunk(
@@ -2195,45 +2190,31 @@ namespace {
21952190

21962191
partialApply->base->walk(*this);
21972192

2198-
// manual clean-up since normal traversal is skipped
2199-
assert(applyStack.back() == apply);
2200-
applyStack.pop_back();
2201-
22022193
return Action::SkipChildren(expr);
22032194
}
22042195
}
2205-
}
2206-
2207-
// NOTE: SelfApplyExpr is a subtype of ApplyExpr
2208-
if (auto call = dyn_cast<SelfApplyExpr>(expr)) {
2209-
Expr *fn = call->getFn()->getValueProvidingExpr();
2210-
if (auto memberRef = findReference(fn)) {
2211-
checkReference(
2212-
call->getBase(), memberRef->first, memberRef->second,
2213-
/*partialApply=*/None, call);
22142196

2215-
call->getBase()->walk(*this);
2197+
applyStack.push_back(apply); // record this encounter
22162198

2199+
if (isa<SelfApplyExpr>(apply)) {
2200+
// Self applications are checked as part of the outer call.
2201+
// However, we look for inout issues here.
22172202
if (applyStack.size() >= 2) {
22182203
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
22192204
if (isAsyncCall(outerCall)) {
22202205
// This call is a partial application within an async call.
22212206
// If the partial application take a value inout, it is bad.
22222207
if (InOutExpr *inoutArg = dyn_cast<InOutExpr>(
2223-
call->getBase()->getSemanticsProvidingExpr()))
2208+
apply->getArgs()->getExpr(0)->getSemanticsProvidingExpr()))
22242209
diagnoseInOutArg(outerCall, inoutArg, true);
22252210
}
22262211
}
2227-
2228-
// manual clean-up since normal traversal is skipped
2229-
assert(applyStack.back() == dyn_cast<ApplyExpr>(expr));
2230-
applyStack.pop_back();
2231-
2232-
return Action::SkipChildren(expr);
2212+
} else {
2213+
// Check the call itself.
2214+
(void)checkApply(apply);
22332215
}
22342216
}
22352217

2236-
22372218
if (auto keyPath = dyn_cast<KeyPathExpr>(expr))
22382219
checkKeyPathExpr(keyPath);
22392220

@@ -2797,26 +2778,69 @@ namespace {
27972778
return *contextIsolation;
27982779
};
27992780

2800-
// If the function type is global-actor-qualified, determine whether
2801-
// we are within that global actor already.
2781+
// Default the call options to allow promotion to async, if it will be
2782+
// warranted.
2783+
ActorReferenceResult::Options callOptions;
2784+
if (!fnType->getExtInfo().isAsync())
2785+
callOptions |= ActorReferenceResult::Flags::AsyncPromotion;
2786+
2787+
// Determine from the callee whether actor isolation is unsatisfied.
28022788
Optional<ActorIsolation> unsatisfiedIsolation;
2789+
bool mayExitToNonisolated = true;
2790+
auto calleeDecl = apply->getCalledValue(/*skipFunctionConversions=*/true);
28032791
if (Type globalActor = fnType->getGlobalActor()) {
2792+
// If the function type is global-actor-qualified, determine whether
2793+
// we are within that global actor already.
28042794
if (!(getContextIsolation().isGlobalActor() &&
28052795
getContextIsolation().getGlobalActor()->isEqual(globalActor))) {
28062796
unsatisfiedIsolation = ActorIsolation::forGlobalActor(
28072797
globalActor, /*unsafe=*/false);
28082798
}
2809-
}
28102799

2811-
if (isa<SelfApplyExpr>(apply) && !unsatisfiedIsolation)
2800+
mayExitToNonisolated = false;
2801+
} else if (auto selfApplyFn = dyn_cast<SelfApplyExpr>(
2802+
apply->getFn()->getValueProvidingExpr())) {
2803+
// If we're calling a member function, check whether the function
2804+
// itself is isolated.
2805+
auto memberFn = selfApplyFn->getFn()->getValueProvidingExpr();
2806+
if (auto memberRef = findReference(memberFn)) {
2807+
auto isolatedActor = getIsolatedActor(selfApplyFn->getBase());
2808+
auto result = ActorReferenceResult::forReference(
2809+
memberRef->first, selfApplyFn->getLoc(), getDeclContext(),
2810+
kindOfUsage(memberRef->first.getDecl(), selfApplyFn),
2811+
isolatedActor, None, None, getClosureActorIsolation);
2812+
switch (result) {
2813+
case ActorReferenceResult::SameConcurrencyDomain:
2814+
break;
2815+
2816+
case ActorReferenceResult::ExitsActorToNonisolated:
2817+
unsatisfiedIsolation = ActorIsolation::forIndependent();
2818+
break;
2819+
2820+
case ActorReferenceResult::EntersActor:
2821+
unsatisfiedIsolation = result.isolation;
2822+
break;
2823+
}
2824+
2825+
callOptions = result.options;
2826+
mayExitToNonisolated = false;
2827+
calleeDecl = memberRef->first.getDecl();
2828+
}
2829+
} else if (calleeDecl &&
2830+
calleeDecl->getAttrs()
2831+
.hasAttribute<UnsafeInheritExecutorAttr>()) {
28122832
return false;
2833+
}
28132834

28142835
// Check for isolated parameters.
2836+
bool anyIsolatedParameters = false;
28152837
for (unsigned paramIdx : range(fnType->getNumParams())) {
28162838
// We only care about isolated parameters.
28172839
if (!fnType->getParams()[paramIdx].isIsolated())
28182840
continue;
28192841

2842+
anyIsolatedParameters = true;
2843+
28202844
auto *args = apply->getArgs();
28212845
if (paramIdx >= args->size())
28222846
continue;
@@ -2840,14 +2864,22 @@ namespace {
28402864
break;
28412865
}
28422866

2867+
// If we're calling an async function that's nonisolated, and we're in
2868+
// an isolated context, then we're exiting the actor context.
2869+
if (mayExitToNonisolated && !anyIsolatedParameters && fnType->isAsync() &&
2870+
getContextIsolation().isActorIsolated())
2871+
unsatisfiedIsolation = ActorIsolation::forIndependent();
2872+
28432873
// If there was no unsatisfied actor isolation, we're done.
28442874
if (!unsatisfiedIsolation)
28452875
return false;
28462876

2877+
bool requiresAsync =
2878+
callOptions.contains(ActorReferenceResult::Flags::AsyncPromotion);
2879+
28472880
// If we are not in an asynchronous context, complain.
2848-
if (!getDeclContext()->isAsyncContext()) {
2849-
if (auto calleeDecl = apply->getCalledValue(
2850-
/*skipFunctionConversions=*/true)) {
2881+
if (requiresAsync && !getDeclContext()->isAsyncContext()) {
2882+
if (calleeDecl) {
28512883
ctx.Diags.diagnose(
28522884
apply->getLoc(), diag::actor_isolated_call_decl,
28532885
*unsatisfiedIsolation,
@@ -2873,8 +2905,8 @@ namespace {
28732905
return true;
28742906
}
28752907

2876-
// Mark as implicitly async.
2877-
if (!fnType->getExtInfo().isAsync() && unsatisfiedIsolation) {
2908+
// If needed, mark as implicitly async.
2909+
if (requiresAsync) {
28782910
apply->setImplicitlyAsync(*unsatisfiedIsolation);
28792911
}
28802912

@@ -2891,11 +2923,13 @@ namespace {
28912923
argLoc = arg.getStartLoc();
28922924
}
28932925

2926+
bool isExiting = !unsatisfiedIsolation->isActorIsolated();
2927+
ActorIsolation diagnoseIsolation = isExiting ? getContextIsolation()
2928+
: *unsatisfiedIsolation;
28942929
if (diagnoseNonSendableTypes(
28952930
param.getParameterType(), getDeclContext(), argLoc,
2896-
diag::non_sendable_call_param_type,
2897-
apply->isImplicitlyAsync().has_value(),
2898-
*unsatisfiedIsolation))
2931+
diag::non_sendable_call_argument,
2932+
isExiting, diagnoseIsolation))
28992933
return true;
29002934
}
29012935

@@ -3088,6 +3122,16 @@ namespace {
30883122
return false;
30893123

30903124
auto decl = declRef.getDecl();
3125+
3126+
// If this declaration is a callee from the enclosing application,
3127+
// it's already been checked via the call.
3128+
if (!applyStack.empty()) {
3129+
auto immediateCallee =
3130+
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3131+
if (decl == immediateCallee)
3132+
return false;
3133+
}
3134+
30913135
Optional<ReferencedActor> isolatedActor;
30923136
if (base)
30933137
isolatedActor.emplace(getIsolatedActor(base));
@@ -5498,7 +5542,7 @@ ActorReferenceResult ActorReferenceResult::forReference(
54985542
declIsolation = declIsolation.subst(declRef.getSubstitutions());
54995543
}
55005544

5501-
// If the entity we are referencing is not a value, we're in thesame
5545+
// If the entity we are referencing is not a value, we're in the same
55025546
// concurrency domain.
55035547
if (isNonValueReference(declRef.getDecl()))
55045548
return forSameConcurrencyDomain(declIsolation);

test/Concurrency/actor_call_implicitly_async.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func regularFunc() {
244244

245245
_ = a.deposit //expected-error{{actor-isolated instance method 'deposit' can not be partially applied}}
246246

247-
_ = a.deposit(1) // expected-error{{actor-isolated instance method 'deposit' can not be referenced from a non-isolated context}}
247+
_ = a.deposit(1) // expected-error{{call to actor-isolated instance method 'deposit' in a synchronous nonisolated context}}
248248
}
249249

250250

@@ -289,29 +289,29 @@ func blender(_ peeler : () -> Void) {
289289

290290

291291
await wisk({})
292-
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
292+
// expected-warning@-1{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
293293
await wisk(1)
294-
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
294+
// expected-warning@-1{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
295295
await (peelBanana)()
296296
await (((((peelBanana)))))()
297297
await (((wisk)))((wisk)((wisk)(1)))
298-
// expected-warning@-1 3{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
298+
// expected-warning@-1 3{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
299299

300300
blender((peelBanana))
301301
// expected-warning@-1 2{{converting function value of type '@BananaActor () -> ()' to '() -> Void' loses global actor 'BananaActor'}}
302302

303303
await wisk(peelBanana)
304-
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
304+
// expected-warning@-1{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
305305

306306
await wisk(wisk)
307-
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
307+
// expected-warning@-1{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
308308
await (((wisk)))(((wisk)))
309-
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
309+
// expected-warning@-1{{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
310310

311-
// expected-warning@+1 {{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
311+
// expected-warning@+1 {{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
312312
await {wisk}()(1)
313313

314-
// expected-warning@+1 {{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
314+
// expected-warning@+1 {{passing argument of non-sendable type 'Any' into global actor 'BananaActor'-isolated context may introduce data races}}
315315
await (true ? wisk : {n in return})(1)
316316
}
317317

@@ -368,14 +368,14 @@ actor Calculator {
368368
let calc = Calculator()
369369

370370
let _ = (await calc.addCurried(1))(2)
371-
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by implicitly asynchronous call to actor-isolated instance method 'addCurried' cannot cross actor boundary}}
371+
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by call to actor-isolated function cannot cross actor boundary}}
372372
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
373373
let _ = await (await calc.addCurried(1))(2) // expected-warning{{no 'async' operations occur within 'await' expression}}
374-
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by implicitly asynchronous call to actor-isolated instance method 'addCurried' cannot cross actor boundary}}
374+
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by call to actor-isolated function cannot cross actor boundary}}
375375
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
376376

377377
let plusOne = await calc.addCurried(await calc.add(0, 1))
378-
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by implicitly asynchronous call to actor-isolated instance method 'addCurried' cannot cross actor boundary}}
378+
// expected-warning@-1{{non-sendable type '(Int) -> Int' returned by call to actor-isolated function cannot cross actor boundary}}
379379
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
380380
let _ = plusOne(2)
381381
}

0 commit comments

Comments
 (0)