Skip to content

Commit 3f68fa1

Browse files
committed
Handle more Expr types in inout actor diags
We were handling weren't handling all of the expression types that can show up as part of a parameter. As a result, the compiler was crashing on correct code. This patch introduces handling for far more expression types and the appropriate tests for each. Added handling includes: - Gentle optional unwrap - Forceful optional unwrap - Identity expressions (await, parens, etc) - Implicit conversion exprs
1 parent ae7e1b5 commit 3f68fa1

File tree

2 files changed

+106
-19
lines changed

2 files changed

+106
-19
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,42 @@ static bool isAsyncCall(const ApplyExpr *call) {
637637
return funcType->isAsync();
638638
}
639639

640+
/// Extract the correct expression for diagnosing actor state being passed inout
641+
///
642+
/// This unwraps most expression types, working toward an offending DeclRefExpr
643+
/// or to the LookupExpr before the DeclRef.
644+
///
645+
/// The return will either be a LookupExpr or a DeclRefExpr.
646+
/// If part of the sub-expression isn't interesting, returns nullptr
647+
static const Expr *getInoutTopExpr(const InOutExpr *arg) {
648+
const Expr * currentExpr = arg->getSubExpr();
649+
while (currentExpr) {
650+
if (const InOutExpr *inout = dyn_cast<InOutExpr>(currentExpr)) {
651+
return nullptr; // The AST walker will hit this again
652+
} else if (const LookupExpr *baseArg = dyn_cast<LookupExpr>(currentExpr)) {
653+
if (isa<DeclRefExpr>(baseArg->getBase()))
654+
return baseArg;
655+
currentExpr = baseArg->getBase();
656+
} else if (const DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(currentExpr))
657+
return declRef;
658+
else if (const BindOptionalExpr *bindingOp =
659+
dyn_cast<BindOptionalExpr>(currentExpr))
660+
currentExpr = bindingOp->getSubExpr();
661+
else if (const ForceValueExpr *forceOp =
662+
dyn_cast<ForceValueExpr>(currentExpr))
663+
currentExpr = forceOp->getSubExpr();
664+
else if (const ImplicitConversionExpr *convExpr =
665+
dyn_cast<ImplicitConversionExpr>(currentExpr))
666+
currentExpr = convExpr->getSubExpr();
667+
else if (const IdentityExpr *identExpr =
668+
dyn_cast<IdentityExpr>(currentExpr))
669+
currentExpr = identExpr->getSubExpr();
670+
else
671+
return currentExpr;
672+
}
673+
llvm_unreachable("The current expression is a nullptr!");
674+
}
675+
640676
/// Determine whether we should diagnose data races within the current context.
641677
///
642678
/// By default, we do this only in code that makes use of concurrency
@@ -958,29 +994,20 @@ namespace {
958994
/// \returns true if we diagnosed the entity, \c false otherwise.
959995
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
960996
bool isPartialApply) {
961-
962997
// check that the call is actually async
963998
if (!isAsyncCall(call))
964999
return false;
9651000

966-
Expr *subArg = arg->getSubExpr();
1001+
const Expr *subArg = getInoutTopExpr(arg);
1002+
if (subArg == nullptr) // Subexpression isn't interesting
1003+
return false;
9671004
ValueDecl *valueDecl = nullptr;
968-
if (auto binding = dyn_cast<BindOptionalExpr>(subArg))
969-
subArg = binding->getSubExpr();
970-
if (LookupExpr *baseArg = dyn_cast<LookupExpr>(subArg)) {
971-
while (LookupExpr *nextLayer = dyn_cast<LookupExpr>(baseArg->getBase()))
972-
baseArg = nextLayer;
973-
// subArg: the actual property being passed inout
974-
// baseArg: the property in the actor who's property is being passed
975-
// inout
976-
977-
valueDecl = baseArg->getMember().getDecl();
978-
} else if (DeclRefExpr *declExpr = dyn_cast<DeclRefExpr>(subArg)) {
1005+
if (const DeclRefExpr *declExpr = dyn_cast<DeclRefExpr>(subArg))
9791006
valueDecl = declExpr->getDecl();
980-
} else {
981-
llvm_unreachable("Inout argument is neither a lookup nor decl.");
982-
}
983-
assert(valueDecl != nullptr && "valueDecl was never set!");
1007+
else if (const LookupExpr * member = dyn_cast<LookupExpr>(subArg))
1008+
valueDecl = member->getMember().getDecl();
1009+
else
1010+
llvm_unreachable("Unhandled expression type");
9841011
auto isolation = ActorIsolationRestriction::forDeclaration(valueDecl);
9851012
switch (isolation) {
9861013
case ActorIsolationRestriction::Unrestricted:

test/Concurrency/actor_inout_isolation.swift

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
struct Point {
1515
var x: Int
1616
var y: Int
17+
var z: Int? = nil
1718

1819
mutating func setComponents(x: inout Int, y: inout Int) async {
1920
defer { (x, y) = (self.x, self.y) }
@@ -22,10 +23,12 @@ struct Point {
2223
}
2324

2425
actor class TestActor {
26+
// expected-note@+1{{mutable state is only available within the actor instance}}
2527
var position = Point(x: 0, y: 0)
2628
var nextPosition = Point(x: 0, y: 1)
2729
var value1: Int = 0
2830
var value2: Int = 1
31+
var points: [Point] = []
2932
}
3033

3134
func modifyAsynchronously(_ foo: inout Int) async { foo += 1 }
@@ -56,6 +59,15 @@ extension TestActor {
5659
// expected-error@+1{{actor-isolated property 'position' cannot be passed 'inout' to 'async' function call}}
5760
await modifyAsynchronously(&position.x)
5861
}
62+
63+
func nestedExprs() async {
64+
// expected-error@+1{{actor-isolated property 'position' cannot be passed 'inout' to 'async' function call}}
65+
await modifyAsynchronously(&position.z!)
66+
67+
// expected-error@+1{{actor-isolated property 'points' cannot be passed 'inout' to 'async' function call}}
68+
await modifyAsynchronously(&points[0].z!)
69+
}
70+
5971
}
6072

6173
// internal method call
@@ -98,8 +110,8 @@ extension TestActor {
98110

99111
func callMutatingFunctionOnStruct() async {
100112
// expected-error@+3:20{{cannot call mutating async function 'setComponents(x:y:)' on actor-isolated property 'position'}}
101-
// expected-error@+2:51{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
102-
// expected-error@+1:71{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
113+
// expected-error@+2:38{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
114+
// expected-error@+1:58{{actor-isolated property 'nextPosition' cannot be passed 'inout' to 'async' function call}}
103115
await position.setComponents(x: &nextPosition.x, y: &nextPosition.y)
104116

105117
// expected-error@+3:20{{cannot call mutating async function 'setComponents(x:y:)' on actor-isolated property 'position'}}
@@ -132,6 +144,40 @@ extension TestActor {
132144
}
133145
}
134146

147+
actor class MyActor {
148+
var points: [Point] = []
149+
var int: Int = 0
150+
var maybeInt: Int?
151+
var maybePoint: Point?
152+
var myActor: TestActor = TestActor()
153+
154+
// Checking that various ways of unwrapping emit the right error messages at
155+
// the right times and that illegal operations are caught
156+
func modifyStuff() async {
157+
// expected-error@+1{{actor-isolated property 'points' cannot be passed 'inout' to 'async' function call}}
158+
await modifyAsynchronously(&points[0].x)
159+
// expected-error@+1{{actor-isolated property 'points' cannot be passed 'inout' to 'async' function call}}
160+
await modifyAsynchronously(&points[0].z!)
161+
// expected-error@+1{{actor-isolated property 'int' cannot be passed 'inout' to 'async' function call}}
162+
await modifyAsynchronously(&int)
163+
// expected-error@+1{{actor-isolated property 'maybeInt' cannot be passed 'inout' to 'async' function call}}
164+
await modifyAsynchronously(&maybeInt!)
165+
// expected-error@+1{{actor-isolated property 'maybePoint' cannot be passed 'inout' to 'async' function call}}
166+
await modifyAsynchronously(&maybePoint!.z!)
167+
// expected-error@+1{{actor-isolated property 'int' cannot be passed 'inout' to 'async' function call}}
168+
await modifyAsynchronously(&(int))
169+
170+
// This warning is emitted because this fails to typecheck before the
171+
// async-ness is attached.
172+
// expected-warning@+2{{no calls to 'async' functions occur within 'await' expression}}
173+
// expected-error@+1{{cannot pass immutable value of type 'Int' as inout argument}}
174+
await modifyAsynchronously(&(maybePoint?.z)!)
175+
// expected-error@+2{{actor-isolated property 'position' can only be referenced inside the actor}}
176+
// expected-error@+1{{actor-isolated property 'myActor' cannot be passed 'inout' to 'async' function call}}
177+
await modifyAsynchronously(&myActor.position.x)
178+
}
179+
}
180+
135181
// Verify global actor protection
136182

137183
@globalActor
@@ -159,3 +205,17 @@ func globalSyncFunction(_ foo: inout Int) { }
159205
@MyGlobalActor func globalActorAsyncOkay() async { globalActorSyncFunction(&number) }
160206
@MyGlobalActor func globalActorAsyncOkay2() async { globalSyncFunction(&number) }
161207
@MyGlobalActor func globalActorSyncOkay() { globalSyncFunction(&number) }
208+
209+
// Gently unwrap things that are fine
210+
struct Cat {
211+
mutating func meow() async { }
212+
}
213+
214+
struct Dog {
215+
var cat: Cat?
216+
217+
mutating func woof() async {
218+
// This used to cause the compiler to crash, but should be fine
219+
await cat?.meow()
220+
}
221+
}

0 commit comments

Comments
 (0)