Skip to content

Commit 1d082e1

Browse files
committed
Require that concurrently-executed local functions be @Concurrent.
1 parent 75f4fb1 commit 1d082e1

File tree

6 files changed

+43
-13
lines changed

6 files changed

+43
-13
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5734,6 +5734,11 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
57345734
/// type of the function will be `async` as well.
57355735
bool hasAsync() const { return Bits.AbstractFunctionDecl.Async; }
57365736

5737+
/// Determine whether the given function is concurrent.
5738+
///
5739+
/// A function is concurrent if it has the @concurrent attribute.
5740+
bool isConcurrent() const;
5741+
57375742
/// Returns true if the function is a suitable 'async' context.
57385743
///
57395744
/// Functions that are an 'async' context can make calls to 'async' functions.

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4261,6 +4261,11 @@ ERROR(actor_isolated_concurrent_access,none,
42614261
"actor-isolated %0 %1 is unsafe to reference in code "
42624262
"that may execute concurrently",
42634263
(DescriptiveDeclKind, DeclName))
4264+
ERROR(local_function_executed_concurrently,none,
4265+
"concurrently-executed %0 %1 must be marked as '@concurrent'",
4266+
(DescriptiveDeclKind, DeclName))
4267+
NOTE(concurrent_access_here,none,
4268+
"access in concurrently-executed code here", ())
42644269
NOTE(actor_isolated_sync_func,none,
42654270
"calls to %0 %1 from outside of its actor context are "
42664271
"implicitly asynchronous",

lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6768,6 +6768,10 @@ bool AbstractFunctionDecl::argumentNameIsAPIByDefault() const {
67686768
return false;
67696769
}
67706770

6771+
bool AbstractFunctionDecl::isConcurrent() const {
6772+
return getAttrs().hasAttribute<ConcurrentAttr>();
6773+
}
6774+
67716775
bool AbstractFunctionDecl::isAsyncHandler() const {
67726776
auto func = dyn_cast<FuncDecl>(this);
67736777
if (!func)

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,14 +1583,30 @@ bool ConcurrentExecutionChecker::mayExecuteConcurrentlyWith(
15831583
return true;
15841584
}
15851585

1586-
// If we find a local function that was referenced in code that can be
1587-
// executed concurrently with where the local function was declared, the
1588-
// local function can be run concurrently.
15891586
if (auto func = dyn_cast<FuncDecl>(useContext)) {
15901587
if (func->isLocalCapture()) {
1588+
// If the function is @concurrent... it can be run concurrently.
1589+
if (func->isConcurrent())
1590+
return true;
1591+
1592+
// If we find a local function that was referenced in code that can be
1593+
// executed concurrently with where the local function was declared, the
1594+
// local function can be run concurrently.
15911595
SourceLoc concurrentLoc = getConcurrentReferenceLoc(func);
1592-
if (concurrentLoc.isValid())
1596+
if (concurrentLoc.isValid()) {
1597+
ASTContext &ctx = func->getASTContext();
1598+
func->diagnose(
1599+
diag::local_function_executed_concurrently,
1600+
func->getDescriptiveKind(), func->getName())
1601+
.fixItInsert(func->getAttributeInsertionLoc(false), "@concurrent ");
1602+
ctx.Diags.diagnose(concurrentLoc, diag::concurrent_access_here);
1603+
1604+
// Add the @concurrent attribute implicitly, so we don't diagnose
1605+
// again.
1606+
const_cast<FuncDecl *>(func)->getAttrs().add(
1607+
new (ctx) ConcurrentAttr(true));
15931608
return true;
1609+
}
15941610
}
15951611
}
15961612

lib/Sema/TypeCheckDecl.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,8 +2524,7 @@ InterfaceTypeRequest::evaluate(Evaluator &eval, ValueDecl *D) const {
25242524
AFD->getParameters()->getParams(argTy);
25252525

25262526
infoBuilder = infoBuilder.withAsync(AFD->hasAsync());
2527-
infoBuilder = infoBuilder.withConcurrent(
2528-
AFD->getAttrs().hasAttribute<ConcurrentAttr>());
2527+
infoBuilder = infoBuilder.withConcurrent(AFD->isConcurrent());
25292528
// 'throws' only applies to the innermost function.
25302529
infoBuilder = infoBuilder.withThrows(AFD->hasThrows());
25312530
// Defer bodies must not escape.

test/Concurrency/actor_isolation.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ func globalFunc() { }
88
func acceptClosure<T>(_: () -> T) { }
99
func acceptConcurrentClosure<T>(_: @concurrent () -> T) { }
1010
func acceptEscapingClosure<T>(_: @escaping () -> T) { }
11-
func acceptEscapingClosure<T>(_: (String) -> ()) async -> T? { nil }
11+
func acceptEscapingClosure<T>(_: @escaping (String) -> ()) async -> T? { nil }
1212

1313
func acceptAsyncClosure<T>(_: () async -> T) { }
1414
func acceptEscapingAsyncClosure<T>(_: @escaping () async -> T) { }
@@ -155,14 +155,14 @@ extension MyActor {
155155
}
156156

157157
// Local functions might run concurrently.
158-
func localFn1() {
158+
@concurrent func localFn1() {
159159
_ = self.text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
160160
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
161161
_ = localVar // expected-warning{{local var 'localVar' is unsafe to reference in code that may execute concurrently}}
162162
_ = localConstant
163163
}
164164

165-
func localFn2() {
165+
@concurrent func localFn2() {
166166
acceptClosure {
167167
_ = text[0] // expected-error{{actor-isolated property 'text' is unsafe to reference in code that may execute concurrently}}
168168
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
@@ -341,8 +341,9 @@ func checkLocalFunctions() async {
341341
i = 17
342342
}
343343

344-
func local2() {
344+
func local2() { // expected-error{{concurrently-executed local function 'local2()' must be marked as '@concurrent'}}{{3-3=@concurrent }}
345345
j = 42 // expected-warning{{local var 'j' is unsafe to reference in code that may execute concurrently}}
346+
// FIXME: the above should be an error as well
346347
}
347348

348349
// Okay to call locally.
@@ -357,7 +358,7 @@ func checkLocalFunctions() async {
357358

358359
// Escaping closures can make the local function execute concurrently.
359360
acceptEscapingClosure {
360-
local2()
361+
local2() // expected-note{{access in concurrently-executed code here}}
361362
}
362363

363364
print(i)
@@ -366,11 +367,11 @@ func checkLocalFunctions() async {
366367
var k = 17 // expected-note{{var declared here}}
367368
func local4() {
368369
acceptEscapingClosure {
369-
local3()
370+
local3() // expected-note{{access in concurrently-executed code here}}
370371
}
371372
}
372373

373-
func local3() {
374+
func local3() { // expected-error{{concurrently-executed local function 'local3()' must be marked as '@concurrent'}}
374375
k = 25 // expected-warning{{local var 'k' is unsafe to reference in code that may execute concurrently}}
375376
}
376377

0 commit comments

Comments
 (0)