Skip to content

Commit 4ca23e1

Browse files
authored
Merge pull request #69126 from hborla/5.10-inout-async-error
[5.10][Concurrency] Fix a few issues with actor-isolated `inout` argument diagnostics
2 parents 2238f04 + 6bf422d commit 4ca23e1

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;
@@ -1950,6 +1960,13 @@ namespace {
19501960
using MutableVarParent
19511961
= llvm::PointerUnion<InOutExpr *, LoadExpr *, AssignExpr *>;
19521962

1963+
ApplyExpr *getImmediateApply() const {
1964+
if (applyStack.empty())
1965+
return nullptr;
1966+
1967+
return applyStack.back().dyn_cast<ApplyExpr *>();
1968+
}
1969+
19531970
const PatternBindingDecl *getTopPatternBindingDecl() const {
19541971
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
19551972
}
@@ -2207,7 +2224,7 @@ namespace {
22072224

22082225
const auto End = applyStack.rend();
22092226
for (auto I = applyStack.rbegin(); I != End; ++I)
2210-
if (auto call = dyn_cast<CallExpr>(*I)) {
2227+
if (auto call = dyn_cast<CallExpr>(I->dyn_cast<ApplyExpr *>())) {
22112228
if (setAsync) {
22122229
call->setImplicitlyAsync(*setAsync);
22132230
}
@@ -2261,6 +2278,11 @@ namespace {
22612278
}
22622279

22632280
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
2281+
// Skip expressions that didn't make it to solution application
2282+
// because the constraint system diagnosed an error.
2283+
if (!expr->getType())
2284+
return Action::SkipChildren(expr);
2285+
22642286
if (auto *openExistential = dyn_cast<OpenExistentialExpr>(expr)) {
22652287
opaqueValues.push_back({
22662288
openExistential->getOpaqueValue(),
@@ -2295,6 +2317,7 @@ namespace {
22952317
recordMutableVarParent(load, load->getSubExpr());
22962318

22972319
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
2320+
applyStack.push_back(lookup);
22982321
checkReference(lookup->getBase(), lookup->getMember(), lookup->getLoc(),
22992322
/*partialApply*/ llvm::None, lookup);
23002323
return Action::Continue(expr);
@@ -2337,7 +2360,7 @@ namespace {
23372360
// Self applications are checked as part of the outer call.
23382361
// However, we look for inout issues here.
23392362
if (applyStack.size() >= 2) {
2340-
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
2363+
auto outerCall = applyStack[applyStack.size() - 2];
23412364
if (isAsyncCall(outerCall)) {
23422365
// This call is a partial application within an async call.
23432366
// If the partial application take a value inout, it is bad.
@@ -2391,17 +2414,21 @@ namespace {
23912414
}
23922415

23932416
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
2394-
assert(applyStack.back() == apply);
2417+
assert(applyStack.back().get<ApplyExpr *>() == apply);
23952418
applyStack.pop_back();
23962419
}
23972420

23982421
// Clear out the mutable local variable parent map on the way out.
2399-
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr))
2422+
if (auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
24002423
mutableLocalVarParent.erase(declRefExpr);
2401-
else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr))
2424+
} else if (auto *lookupExpr = dyn_cast<LookupExpr>(expr)) {
24022425
mutableLocalVarParent.erase(lookupExpr);
2403-
else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr))
2426+
2427+
assert(applyStack.back().dyn_cast<LookupExpr *>() == lookupExpr);
2428+
applyStack.pop_back();
2429+
} else if (auto *inoutExpr = dyn_cast<InOutExpr>(expr)) {
24042430
mutableLocalVarParent.erase(inoutExpr);
2431+
}
24052432

24062433
// Remove the tracked capture contexts.
24072434
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
@@ -2616,28 +2643,31 @@ namespace {
26162643
/// Diagnose an inout argument passed into an async call
26172644
///
26182645
/// \returns true if we diagnosed the entity, \c false otherwise.
2619-
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
2620-
bool isPartialApply) {
2646+
bool diagnoseInOutArg(
2647+
llvm::PointerUnion<ApplyExpr *, LookupExpr *> call,
2648+
const InOutExpr *arg,
2649+
bool isPartialApply) {
26212650
// check that the call is actually async
26222651
if (!isAsyncCall(call))
26232652
return false;
26242653

26252654
bool result = false;
2626-
auto checkDiagnostic = [this, call, isPartialApply, &result](
2655+
auto diagnoseIsolatedInoutState = [this, call, isPartialApply, &result](
26272656
ConcreteDeclRef declRef, SourceLoc argLoc) {
26282657
auto decl = declRef.getDecl();
26292658
auto isolation = getActorIsolationForReference(decl, getDeclContext());
26302659
if (!isolation.isActorIsolated())
26312660
return;
26322661

26332662
if (isPartialApply) {
2663+
auto *apply = call.get<ApplyExpr *>();
26342664
// The partially applied InoutArg is a property of actor. This
26352665
// can really only happen when the property is a struct with a
26362666
// mutating async method.
2637-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
2667+
if (auto partialApply = dyn_cast<ApplyExpr>(apply->getFn())) {
26382668
if (auto declRef = dyn_cast<DeclRefExpr>(partialApply->getFn())) {
26392669
ValueDecl *fnDecl = declRef->getDecl();
2640-
ctx.Diags.diagnose(call->getLoc(),
2670+
ctx.Diags.diagnose(apply->getLoc(),
26412671
diag::actor_isolated_mutating_func,
26422672
fnDecl->getName(), decl);
26432673
result = true;
@@ -2646,29 +2676,36 @@ namespace {
26462676
}
26472677
}
26482678

2679+
bool isImplicitlyAsync;
2680+
if (auto *apply = call.dyn_cast<ApplyExpr *>()) {
2681+
isImplicitlyAsync = apply->isImplicitlyAsync().has_value();
2682+
} else {
2683+
auto *lookup = call.get<LookupExpr *>();
2684+
isImplicitlyAsync = lookup->isImplicitlyAsync().has_value();
2685+
}
2686+
26492687
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
2650-
decl, call->isImplicitlyAsync().has_value());
2688+
decl, isImplicitlyAsync);
26512689
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
26522690
result = true;
26532691
return;
26542692
};
2655-
auto expressionWalker = [baseArg = arg->getSubExpr(),
2656-
checkDiagnostic](Expr *expr) -> Expr * {
2657-
if (isa<InOutExpr>(expr))
2658-
return nullptr; // AST walker will hit this again
2693+
2694+
auto findIsolatedState = [&](Expr *expr) -> Expr * {
26592695
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
26602696
if (isa<DeclRefExpr>(lookup->getBase())) {
2661-
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
2697+
diagnoseIsolatedInoutState(lookup->getMember().getDecl(),
2698+
expr->getLoc());
26622699
return nullptr; // Diagnosed. Don't keep walking
26632700
}
26642701
}
26652702
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
2666-
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
2703+
diagnoseIsolatedInoutState(declRef->getDecl(), expr->getLoc());
26672704
return nullptr; // Diagnosed. Don't keep walking
26682705
}
26692706
return expr;
26702707
};
2671-
arg->getSubExpr()->forEachChildExpr(expressionWalker);
2708+
arg->getSubExpr()->forEachChildExpr(findIsolatedState);
26722709
return result;
26732710
}
26742711

@@ -3172,9 +3209,9 @@ namespace {
31723209

31733210
// If this declaration is a callee from the enclosing application,
31743211
// it's already been checked via the call.
3175-
if (!applyStack.empty()) {
3212+
if (auto *apply = getImmediateApply()) {
31763213
auto immediateCallee =
3177-
applyStack.back()->getCalledValue(/*skipFunctionConversions=*/true);
3214+
apply->getCalledValue(/*skipFunctionConversions=*/true);
31783215
if (decl == immediateCallee)
31793216
return false;
31803217
}

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)