Skip to content

Commit 3efa18f

Browse files
authored
Merge pull request #35325 from etcwilde/ewilde/handle-more-exprs-for-inout-checking
[Concurrency] Handle more expr types for inout checking
2 parents fe4d46c + 3a3aaf1 commit 3efa18f

File tree

2 files changed

+117
-56
lines changed

2 files changed

+117
-56
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 57 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -958,68 +958,69 @@ namespace {
958958
/// \returns true if we diagnosed the entity, \c false otherwise.
959959
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
960960
bool isPartialApply) {
961-
962961
// check that the call is actually async
963962
if (!isAsyncCall(call))
964963
return false;
965964

966-
Expr *subArg = arg->getSubExpr();
967-
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)) {
979-
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!");
984-
auto isolation = ActorIsolationRestriction::forDeclaration(valueDecl);
985-
switch (isolation) {
986-
case ActorIsolationRestriction::Unrestricted:
987-
case ActorIsolationRestriction::LocalCapture:
988-
case ActorIsolationRestriction::Unsafe:
989-
break;
990-
case ActorIsolationRestriction::GlobalActor: {
991-
ctx.Diags.diagnose(call->getLoc(),
992-
diag::actor_isolated_inout_state,
993-
valueDecl->getDescriptiveKind(),
994-
valueDecl->getName(),
995-
call->implicitlyAsync());
996-
valueDecl->diagnose(diag::kind_declared_here,
997-
valueDecl->getDescriptiveKind());
998-
return true;
999-
}
1000-
case ActorIsolationRestriction::ActorSelf: {
1001-
if (isPartialApply) {
1002-
// The partially applied InoutArg is a property of actor. This can
1003-
// really only happen when the property is a struct with a mutating
1004-
// async method.
1005-
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
1006-
ValueDecl *fnDecl =
1007-
cast<DeclRefExpr>(partialApply->getFn())->getDecl();
1008-
ctx.Diags.diagnose(
1009-
call->getLoc(), diag::actor_isolated_mutating_func,
1010-
fnDecl->getName(), valueDecl->getDescriptiveKind(),
1011-
valueDecl->getName());
1012-
return true;
965+
bool result = false;
966+
auto checkDiagnostic = [this, call, isPartialApply,
967+
&result](ValueDecl *decl, SourceLoc argLoc) {
968+
auto isolation = ActorIsolationRestriction::forDeclaration(decl);
969+
switch (isolation) {
970+
case ActorIsolationRestriction::Unrestricted:
971+
case ActorIsolationRestriction::LocalCapture:
972+
case ActorIsolationRestriction::Unsafe:
973+
break;
974+
case ActorIsolationRestriction::GlobalActor: {
975+
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
976+
decl->getDescriptiveKind(), decl->getName(),
977+
call->implicitlyAsync());
978+
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
979+
result = true;
980+
break;
981+
}
982+
case ActorIsolationRestriction::ActorSelf: {
983+
if (isPartialApply) {
984+
// The partially applied InoutArg is a property of actor. This
985+
// can really only happen when the property is a struct with a
986+
// mutating async method.
987+
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
988+
ValueDecl *fnDecl =
989+
cast<DeclRefExpr>(partialApply->getFn())->getDecl();
990+
ctx.Diags.diagnose(call->getLoc(),
991+
diag::actor_isolated_mutating_func,
992+
fnDecl->getName(), decl->getDescriptiveKind(),
993+
decl->getName());
994+
result = true;
995+
}
996+
} else {
997+
ctx.Diags.diagnose(argLoc, diag::actor_isolated_inout_state,
998+
decl->getDescriptiveKind(), decl->getName(),
999+
call->implicitlyAsync());
1000+
result = true;
10131001
}
1014-
} else {
1015-
ctx.Diags.diagnose(
1016-
subArg->getLoc(), diag::actor_isolated_inout_state,
1017-
valueDecl->getDescriptiveKind(), valueDecl->getName(), call->implicitlyAsync());
1018-
return true;
1002+
break;
10191003
}
1020-
}
1021-
}
1022-
return false;
1004+
}
1005+
};
1006+
auto expressionWalker = [baseArg = arg->getSubExpr(),
1007+
checkDiagnostic](Expr *expr) -> Expr * {
1008+
if (isa<InOutExpr>(expr))
1009+
return nullptr; // AST walker will hit this again
1010+
if (LookupExpr *lookup = dyn_cast<LookupExpr>(expr)) {
1011+
if (isa<DeclRefExpr>(lookup->getBase())) {
1012+
checkDiagnostic(lookup->getMember().getDecl(), baseArg->getLoc());
1013+
return nullptr; // Diagnosed. Don't keep walking
1014+
}
1015+
}
1016+
if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) {
1017+
checkDiagnostic(declRef->getDecl(), baseArg->getLoc());
1018+
return nullptr; // Diagnosed. Don't keep walking
1019+
}
1020+
return expr;
1021+
};
1022+
arg->getSubExpr()->forEachChildExpr(expressionWalker);
1023+
return result;
10231024
}
10241025

10251026
/// Get the actor isolation of the innermost relevant context.

test/Concurrency/actor_inout_isolation.swift

Lines changed: 60 additions & 0 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
@@ -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)