Skip to content

Commit 9904026

Browse files
committed
[Concurrency] Teach data race & isolation checking about @Concurrent functions.
@Concurrent functions are, of course, concurrent. Treat them that way.
1 parent b77ba9a commit 9904026

File tree

2 files changed

+28
-7
lines changed

2 files changed

+28
-7
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,16 @@ static bool isEscapingClosure(const AbstractClosureExpr *closure) {
653653
return true;
654654
}
655655

656+
/// Determine whether this closure is escaping.
657+
static bool isConcurrentClosure(const AbstractClosureExpr *closure) {
658+
if (auto type = closure->getType()) {
659+
if (auto fnType = type->getAs<AnyFunctionType>())
660+
return fnType->isConcurrent();
661+
}
662+
663+
return false;
664+
}
665+
656666
namespace {
657667
/// Check whether a particular context may execute concurrently within
658668
/// another context.
@@ -1370,8 +1380,8 @@ namespace {
13701380
/// isolation checked.
13711381
ClosureActorIsolation determineClosureIsolation(
13721382
AbstractClosureExpr *closure) {
1373-
// An escaping closure is always actor-independent.
1374-
if (isEscapingClosure(closure))
1383+
// Escaping and concurrent closures are always actor-independent.
1384+
if (isEscapingClosure(closure) || isConcurrentClosure(closure))
13751385
return ClosureActorIsolation::forIndependent();
13761386

13771387
// A non-escaping closure gets its isolation from its context.
@@ -1565,9 +1575,11 @@ bool ConcurrentExecutionChecker::mayExecuteConcurrentlyWith(
15651575
const DeclContext *useContext, const DeclContext *defContext) {
15661576
// Walk the context chain from the use to the definition.
15671577
while (useContext != defContext) {
1568-
// If we find an escaping closure, it can be run concurrently.
1578+
// If we find a concurrent closure... it can be run concurrently.
1579+
// NOTE: We also classify escaping closures this way, which detects more
1580+
// problematic cases.
15691581
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
1570-
if (isEscapingClosure(closure))
1582+
if (isEscapingClosure(closure) || isConcurrentClosure(closure))
15711583
return true;
15721584
}
15731585

test/Concurrency/actor_isolation.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var mutableGlobal: String = "can't touch this" // expected-note 3{{var declared
66

77
func globalFunc() { }
88
func acceptClosure<T>(_: () -> T) { }
9+
func acceptConcurrentClosure<T>(_: @concurrent () -> T) { }
910
func acceptEscapingClosure<T>(_: @escaping () -> T) { }
1011
func acceptEscapingClosure<T>(_: (String) -> ()) async -> T? { nil }
1112

@@ -29,12 +30,12 @@ actor class MySuperActor {
2930

3031
actor class MyActor: MySuperActor {
3132
let immutable: Int = 17
32-
var text: [String] = [] // expected-note 9{{mutable state is only available within the actor instance}}
33+
var text: [String] = [] // expected-note 10{{mutable state is only available within the actor instance}}
3334

3435
class func synchronousClass() { }
3536
static func synchronousStatic() { }
3637

37-
func synchronous() -> String { text.first ?? "nothing" } // expected-note 20{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
38+
func synchronous() -> String { text.first ?? "nothing" } // expected-note 21{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
3839
func asynchronous() async -> String { synchronous() }
3940
}
4041

@@ -127,7 +128,7 @@ extension MyActor {
127128

128129
// Closures.
129130
let localConstant = 17
130-
var localVar = 17 // expected-note 3{{var declared here}}
131+
var localVar = 17 // expected-note 4{{var declared here}}
131132

132133
// Non-escaping closures are okay.
133134
acceptClosure {
@@ -137,6 +138,14 @@ extension MyActor {
137138
_ = localConstant
138139
}
139140

141+
// Concurrent closures might run... concurrently.
142+
acceptConcurrentClosure {
143+
_ = self.text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
144+
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
145+
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
146+
_ = localConstant
147+
}
148+
140149
// Escaping closures might run concurrently.
141150
acceptEscapingClosure {
142151
_ = self.text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}

0 commit comments

Comments
 (0)