Skip to content

Commit 82272c8

Browse files
authored
Merge pull request #69083 from hborla/inout-async-error
[Concurrency] Fix a few issues with actor-isolated `inout` argument diagnostics.
2 parents d90470d + 17ac5fe commit 82272c8

File tree

2 files changed

+115
-36
lines changed

2 files changed

+115
-36
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -556,20 +556,30 @@ findReference(Expr *expr) {
556556
///
557557
/// Note that this must be called after the implicitlyAsync flag has been set,
558558
/// or implicitly async calls will not return the correct value.
559-
static bool isAsyncCall(const ApplyExpr *call) {
560-
if (call->isImplicitlyAsync())
559+
static bool isAsyncCall(
560+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call) {
561+
562+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
563+
if (apply->isImplicitlyAsync())
564+
return true;
565+
566+
// Effectively the same as doing a
567+
// `cast_or_null<FunctionType>(call->getFn()->getType())`, check the
568+
// result of that and then checking `isAsync` if it's defined.
569+
Type funcTypeType = apply->getFn()->getType();
570+
if (!funcTypeType)
571+
return false;
572+
AnyFunctionType *funcType = funcTypeType->getAs<AnyFunctionType>();
573+
if (!funcType)
574+
return false;
575+
return funcType->isAsync();
576+
}
577+
578+
auto *lookup = call.get<LookupExpr *>();
579+
if (lookup->isImplicitlyAsync())
561580
return true;
562581

563-
// Effectively the same as doing a
564-
// `cast_or_null<FunctionType>(call->getFn()->getType())`, check the
565-
// result of that and then checking `isAsync` if it's defined.
566-
Type funcTypeType = call->getFn()->getType();
567-
if (!funcTypeType)
568-
return false;
569-
AnyFunctionType *funcType = funcTypeType->getAs<AnyFunctionType>();
570-
if (!funcType)
571-
return false;
572-
return funcType->isAsync();
582+
return isAsyncDecl(lookup->getDecl());
573583
}
574584

575585
/// Determine whether we should diagnose data races within the current context.
@@ -1932,7 +1942,7 @@ namespace {
19321942
class ActorIsolationChecker : public ASTWalker {
19331943
ASTContext &ctx;
19341944
SmallVector<const DeclContext *, 4> contextStack;
1935-
SmallVector<ApplyExpr*, 4> applyStack;
1945+
SmallVector<llvm::PointerUnion<ApplyExpr *, LookupExpr *>, 4> applyStack;
19361946
SmallVector<std::pair<OpaqueValueExpr *, Expr *>, 4> opaqueValues;
19371947
SmallVector<const PatternBindingDecl *, 2> patternBindingStack;
19381948
llvm::function_ref<Type(Expr *)> getType;
@@ -1956,6 +1966,13 @@ namespace {
19561966
using MutableVarParent
19571967
= llvm::PointerUnion<InOutExpr *, LoadExpr *, AssignExpr *>;
19581968

1969+
ApplyExpr *getImmediateApply() const {
1970+
if (applyStack.empty())
1971+
return nullptr;
1972+
1973+
return applyStack.back().dyn_cast<ApplyExpr *>();
1974+
}
1975+
19591976
const PatternBindingDecl *getTopPatternBindingDecl() const {
19601977
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
19611978
}
@@ -2312,7 +2329,7 @@ namespace {
23122329

23132330
const auto End = applyStack.rend();
23142331
for (auto I = applyStack.rbegin(); I != End; ++I)
2315-
if (auto call = dyn_cast<CallExpr>(*I)) {
2332+
if (auto call = dyn_cast<CallExpr>(I->dyn_cast<ApplyExpr *>())) {
23162333
if (setAsync) {
23172334
call->setImplicitlyAsync(*setAsync);
23182335
}
@@ -2366,6 +2383,11 @@ namespace {
23662383
}
23672384

23682385
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
2386+
// Skip expressions that didn't make it to solution application
2387+
// because the constraint system diagnosed an error.
2388+
if (!expr->getType())
2389+
return Action::SkipChildren(expr);
2390+
23692391
if (auto *openExistential = dyn_cast<OpenExistentialExpr>(expr)) {
23702392
opaqueValues.push_back({
23712393
openExistential->getOpaqueValue(),
@@ -2400,6 +2422,7 @@ namespace {
24002422
recordMutableVarParent(load, load->getSubExpr());
24012423

24022424
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2425+
applyStack.push_back(lookup);
24032426
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
24042427
/*partialApply*/ llvm::None, lookup);
24052428
return Action::Continue(expr);
@@ -2442,7 +2465,7 @@ namespace {
24422465
// Self applications are checked as part of the outer call.
24432466
// However, we look for inout issues here.
24442467
if (applyStack.size() >= 2) {
2445-
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
2468+
auto outerCall = applyStack[applyStack.size() - 2];
24462469
if (isAsyncCall(outerCall)) {
24472470
// This call is a partial application within an async call.
24482471
// If the partial application take a value inout, it is bad.
@@ -2500,17 +2523,21 @@ namespace {
25002523
}
25012524

25022525
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
2503-
assert(applyStack.back() == apply);
2526+
assert(applyStack.back().get<ApplyExpr *>() == apply);
25042527
applyStack.pop_back();
25052528
}
25062529

25072530
// Clear out the mutable local variable parent map on the way out.
2508-
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
2531+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
25092532
mutableLocalVarParent.erase(declRefExpr);
2510-
else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr))
2533+
} else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr)) {
25112534
mutableLocalVarParent.erase(lookupExpr);
2512-
else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr))
2535+
2536+
assert(applyStack.back().dyn_cast<LookupExpr *>() == lookupExpr);
2537+
applyStack.pop_back();
2538+
} else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
25132539
mutableLocalVarParent.erase(inoutExpr);
2540+
}
25142541

25152542
// Remove the tracked capture contexts.
25162543
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
@@ -2725,28 +2752,31 @@ namespace {
27252752
/// Diagnose an inout argument passed into an async call
27262753
///
27272754
/// \returns true if we diagnosed the entity, \c false otherwise.
2728-
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
2729-
bool isPartialApply) {
2755+
bool diagnoseInOutArg(
2756+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call,
2757+
const InOutExpr *arg,
2758+
bool isPartialApply) {
27302759
// check that the call is actually async
27312760
if (!isAsyncCall(call))
27322761
return false;
27332762

27342763
bool result = false;
2735-
auto checkDiagnostic = [this, call, isPartialApply, &result](
2764+
auto diagnoseIsolatedInoutState = [this, call, isPartialApply, &result](
27362765
ConcreteDeclRef declRef, SourceLoc argLoc) {
27372766
auto decl = declRef.getDecl();
27382767
auto isolation = getActorIsolationForReference(decl, getDeclContext());
27392768
if (!isolation.isActorIsolated())
27402769
return;
27412770

27422771
if (isPartialApply) {
2772+
auto *apply = call.get<ApplyExpr *>();
27432773
// The partially applied InoutArg is a property of actor. This
27442774
// can really only happen when the property is a struct with a
27452775
// mutating async method.
2746-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
2776+
if (auto partialApply = dyn_cast<ApplyExpr>(apply->getFn())) {
27472777
if (auto declRef = dyn_cast<DeclRefExpr>(partialApply->getFn())) {
27482778
ValueDecl *fnDecl = declRef->getDecl();
2749-
ctx.Diags.diagnose(call->getLoc(),
2779+
ctx.Diags.diagnose(apply->getLoc(),
27502780
diag::actor_isolated_mutating_func,
27512781
fnDecl->getName(), decl);
27522782
result = true;
@@ -2755,29 +2785,36 @@ namespace {
27552785
}
27562786
}
27572787

2788+
bool isImplicitlyAsync;
2789+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
2790+
isImplicitlyAsync = apply->isImplicitlyAsync().has_value();
2791+
} else {
2792+
auto *lookup = call.get<LookupExpr *>();
2793+
isImplicitlyAsync = lookup->isImplicitlyAsync().has_value();
2794+
}
2795+
27582796
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
2759-
decl, call->isImplicitlyAsync().has_value());
2797+
decl, isImplicitlyAsync);
27602798
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
27612799
result = true;
27622800
return;
27632801
};
2764-
auto expressionWalker = [baseArg = arg->getSubExpr(),
2765-
checkDiagnostic](Expr *expr) -> Expr * {
2766-
if (isa<InOutExpr>(expr))
2767-
return nullptr; // AST walker will hit this again
2802+
2803+
auto findIsolatedState = [&](Expr *expr) -> Expr * {
27682804
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
27692805
if (isa<DeclRefExpr>(lookup->getBase())) {
2770-
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
2806+
diagnoseIsolatedInoutState(lookup->getMember().getDecl(),
2807+
expr->getLoc());
27712808
return nullptr; // Diagnosed. Don't keep walking
27722809
}
27732810
}
27742811
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
2775-
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
2812+
diagnoseIsolatedInoutState(declRef->getDecl(), expr->getLoc());
27762813
return nullptr; // Diagnosed. Don't keep walking
27772814
}
27782815
return expr;
27792816
};
2780-
arg->getSubExpr()->forEachChildExpr(expressionWalker);
2817+
arg->getSubExpr()->forEachChildExpr(findIsolatedState);
27812818
return result;
27822819
}
27832820

@@ -3284,9 +3321,9 @@ namespace {
32843321

32853322
// If this declaration is a callee from the enclosing application,
32863323
// it's already been checked via the call.
3287-
if (!applyStack.empty()) {
3324+
if (auto *apply = getImmediateApply()) {
32883325
auto immediateCallee =
3289-
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3326+
apply->getCalledValue(/*skipFunctionConversions=*/true);
32903327
if (decl == immediateCallee)
32913328
return false;
32923329
}

test/Concurrency/actor_inout_isolation.swift

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ extension TestActor {
136136
func callMutatingFunctionOnStruct() async {
137137
// expected-targeted-complete-warning @+4 {{passing argument of non-sendable type 'inout Point' outside of actor-isolated context may introduce data races}}
138138
// expected-error@+3:20{{cannot call mutating async function 'setComponents(x:y:)' on actor-isolated property 'position'}}
139-
// expected-error@+2:51{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
140-
// expected-error@+1:71{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
139+
// expected-error@+2:38{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
140+
// expected-error@+1:58{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
141141
await position.setComponents(x: &nextPosition.x, y: &nextPosition.y)
142142

143143
// expected-targeted-complete-warning @+4 {{passing argument of non-sendable type 'inout Point' outside of actor-isolated context may introduce data races}}
@@ -265,3 +265,45 @@ struct Dog {
265265
await cat?.meow()
266266
}
267267
}
268+
269+
@available(SwiftStdlib 5.1, *)
270+
func passToAsync(_: Int) async {}
271+
272+
@available(SwiftStdlib 5.1, *)
273+
func wrapInClosure(
274+
@_inheritActorContext _ block: @Sendable () async throws -> Void
275+
) async {}
276+
277+
@available(SwiftStdlib 5.1, *)
278+
extension Array {
279+
var mutateAsynchronously: Int {
280+
mutating get async { 0 }
281+
}
282+
283+
subscript(mutateAsynchronously i: Int) -> Int {
284+
mutating get async { 0 }
285+
}
286+
}
287+
288+
@available(SwiftStdlib 5.1, *)
289+
actor ProtectArray {
290+
var array: [Int] = []
291+
// expected-note@-1 {{property declared here}}
292+
293+
func test() async {
294+
// FIXME: this is invalid too!
295+
_ = await array.mutateAsynchronously
296+
// expected-targeted-complete-sns-warning@-1 {{non-sendable type '@lvalue [Int]' exiting actor-isolated context in call to non-isolated property 'mutateAsynchronously' cannot cross actor boundary}}
297+
298+
_ = await array[mutateAsynchronously: 0]
299+
// expected-error@-1 {{actor-isolated property 'array' cannot be passed 'inout' to 'async' function call}}
300+
// expected-targeted-complete-sns-warning@-2 {{non-sendable type 'inout Array<Int>' exiting actor-isolated context in call to non-isolated subscript 'subscript(mutateAsynchronously:)' cannot cross actor boundary}}
301+
302+
await passToAsync(array[0])
303+
304+
await wrapInClosure {
305+
_ = array[0]
306+
array.append(1)
307+
}
308+
}
309+
}

0 commit comments

Comments
 (0)