Skip to content

Commit 099de9d

Browse files
committed
[Concurrency] Assume non-escaping closures don't run concurrently.
Non-escaping closures are assumed to run non-concurrently, so don't complain about accesses to actor-isolated state on self within them.
1 parent 79e6c61 commit 099de9d

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -483,12 +483,32 @@ void swift::checkActorIsolation(const Expr *expr, const DeclContext *dc) {
483483
/// concurrently with code in the definition context.
484484
bool mayExecuteConcurrentlyWith(
485485
const DeclContext *useContext, const DeclContext *defContext) {
486-
// If it's the same context, it won't execute concurrently.
487-
if (useContext == defContext)
488-
return false;
489486

490-
// Assume all child contexts can execute concurrently.
491-
return true;
487+
// Walk the context chain from the use to the definition.
488+
while (useContext != defContext) {
489+
// If we find an escaping closure, it can be run concurrently.
490+
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
491+
if (auto type = closure->getType()) {
492+
if (auto fnType = type->getAs<AnyFunctionType>())
493+
if (!fnType->isNoEscape())
494+
return true;
495+
}
496+
}
497+
498+
// If we find a local function, it can escape and be run concurrently.
499+
if (auto func = dyn_cast<AbstractFunctionDecl>(useContext)) {
500+
if (func->isLocalCapture())
501+
return true;
502+
}
503+
504+
// If we hit a module-scope context, it's not concurrent.
505+
useContext = useContext->getParent();
506+
if (useContext->isModuleScopeContext())
507+
return false;
508+
}
509+
510+
// We hit the same context, so it won't execute concurrently.
511+
return false;
492512
}
493513

494514
// Retrieve the nearest enclosing actor context.

test/Concurrency/actor_isolation.swift

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ actor class MySuperActor {
2323

2424
actor class MyActor: MySuperActor {
2525
let immutable: Int = 17
26-
var text: [String] = [] // expected-note 4{{mutable state is only available within the actor instance}}
26+
var text: [String] = [] // expected-note 5{{mutable state is only available within the actor instance}}
2727

2828
class func synchronousClass() { }
2929
static func synchronousStatic() { }
3030

31-
func synchronous() -> String { text.first ?? "nothing" } // expected-note 4{{only asynchronous methods can be used outside the actor instance; do you want to add 'async'?}}
31+
func synchronous() -> String { text.first ?? "nothing" } // expected-note 5{{only asynchronous methods can be used outside the actor instance; do you want to add 'async'?}}
3232
func asynchronous() async -> String { synchronous() }
3333
}
3434

@@ -71,20 +71,41 @@ extension MyActor {
7171

7272
// Closures.
7373
let localConstant = 17
74-
var localVar = 17 // expected-note 2{{var declared here}}
74+
var localVar = 17 // expected-note 3{{var declared here}}
75+
76+
// Non-escaping closures are okay.
7577
acceptClosure {
76-
_ = text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
78+
_ = text[0]
79+
_ = self.synchronous()
80+
_ = localVar
81+
_ = localConstant
82+
}
83+
84+
// Escaping closures might run concurrently.
85+
acceptEscapingClosure {
86+
_ = self.text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
7787
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
7888
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
7989
_ = localConstant
8090
}
8191

82-
acceptEscapingClosure {
92+
// Local functions might run concurrently.
93+
func localFn1() {
8394
_ = self.text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
8495
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
8596
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
8697
_ = localConstant
8798
}
99+
100+
func localFn2() {
101+
acceptClosure {
102+
_ = text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
103+
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
104+
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
105+
_ = localConstant
106+
}
107+
}
108+
88109
localVar = 0
89110
}
90111
}

0 commit comments

Comments
 (0)