Skip to content

Commit 3a9ddf2

Browse files
authored
Merge pull request #35927 from DougGregor/concurrentvalue-classes
2 parents 9b1eccc + 2141bb0 commit 3a9ddf2

13 files changed

+111
-41
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4274,9 +4274,10 @@ ERROR(actor_isolated_from_escaping_closure,none,
42744274
ERROR(local_function_executed_concurrently,none,
42754275
"concurrently-executed %0 %1 must be marked as '@concurrent'",
42764276
(DescriptiveDeclKind, DeclName))
4277-
ERROR(concurrent_mutation_of_local_capture,none,
4278-
"mutation of captured %0 %1 in concurrently-executing code",
4279-
(DescriptiveDeclKind, DeclName))
4277+
ERROR(concurrent_access_of_local_capture,none,
4278+
"%select{mutation of|reference to}0 captured %1 %2 in "
4279+
"concurrently-executing code",
4280+
(bool, DescriptiveDeclKind, DeclName))
42804281
NOTE(actor_isolated_sync_func,none,
42814282
"calls to %0 %1 from outside of its actor context are "
42824283
"implicitly asynchronous",
@@ -4323,6 +4324,13 @@ ERROR(concurrent_value_outside_source_file,none,
43234324
"conformance 'ConcurrentValue' must occur in the same source file as "
43244325
"%0 %1; use 'UnsafeConcurrentValue' for retroactive conformance",
43254326
(DescriptiveDeclKind, DeclName))
4327+
ERROR(concurrent_value_open_class,none,
4328+
"open class %0 cannot conform to `ConcurrentValue`; "
4329+
"use `UnsafeConcurrentValue`", (DeclName))
4330+
ERROR(concurrent_value_inherit,none,
4331+
"`ConcurrentValue` class %1 cannot inherit from another class"
4332+
"%select{| other than 'NSObject'}0",
4333+
(bool, DeclName))
43264334

43274335
ERROR(actorindependent_let,none,
43284336
"'@actorIndependent' is meaningless on 'let' declarations because "

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ namespace swift {
247247
/// Enable experimental ConcurrentValue checking.
248248
bool EnableExperimentalConcurrentValueChecking = false;
249249

250+
/// Enable experimental flow-sensitive concurrent captures.
251+
bool EnableExperimentalFlowSensitiveConcurrentCaptures = false;
252+
250253
/// Disable the implicit import of the _Concurrency module.
251254
bool DisableImplicitConcurrencyModuleImport = false;
252255

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ def enable_experimental_concurrent_value_checking :
226226
Flag<["-"], "enable-experimental-concurrent-value-checking">,
227227
HelpText<"Enable ConcurrentValue checking">;
228228

229+
def enable_experimental_flow_sensitive_concurrent_captures :
230+
Flag<["-"], "enable-experimental-flow-sensitive-concurrent-captures">,
231+
HelpText<"Enable flow-sensitive concurrent captures">;
232+
229233
def enable_resilience : Flag<["-"], "enable-resilience">,
230234
HelpText<"Deprecated, use -enable-library-evolution instead">;
231235
}

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
387387
Args.hasArg(OPT_enable_experimental_concurrency);
388388
Opts.EnableExperimentalConcurrentValueChecking |=
389389
Args.hasArg(OPT_enable_experimental_concurrent_value_checking);
390+
Opts.EnableExperimentalFlowSensitiveConcurrentCaptures |=
391+
Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures);
390392

391393
Opts.DisableImplicitConcurrencyModuleImport |=
392394
Args.hasArg(OPT_disable_implicit_concurrency_module_import);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
129129
IGNORED_ATTR(OriginallyDefinedIn)
130130
IGNORED_ATTR(NoDerivative)
131131
IGNORED_ATTR(SpecializeExtension)
132+
IGNORED_ATTR(Concurrent)
132133
#undef IGNORED_ATTR
133134

134135
void visitAlignmentAttr(AlignmentAttr *attr) {
@@ -420,22 +421,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
420421
}
421422
}
422423
}
423-
424-
void visitConcurrentAttr(ConcurrentAttr *attr) {
425-
auto VD = dyn_cast<ValueDecl>(D);
426-
if (!VD)
427-
return;
428-
429-
auto innermostDC = VD->getInnermostDeclContext();
430-
SubstitutionMap subs;
431-
if (auto genericEnv = innermostDC->getGenericEnvironmentOfContext()) {
432-
subs = genericEnv->getForwardingSubstitutionMap();
433-
}
434-
435-
(void)diagnoseNonConcurrentTypesInReference(
436-
ConcreteDeclRef(VD, subs), innermostDC, VD->getLoc(),
437-
ConcurrentReferenceKind::ConcurrentFunction);
438-
}
439424
};
440425
} // end anonymous namespace
441426

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,21 +1605,28 @@ namespace {
16051605
return false;
16061606

16071607
// Check whether this is a local variable, in which case we can
1608-
// determine whether it was captured by value.
1608+
// determine whether it was safe to access concurrently.
16091609
if (auto var = dyn_cast<VarDecl>(value)) {
16101610
auto parent = mutableLocalVarParent[declRefExpr];
16111611

1612-
// If we have an immediate load of this variable, or it's a let,
1613-
// we will separately ensure that this variable is not modified.
1614-
if (!var->supportsMutation() || parent.dyn_cast<LoadExpr *>()) {
1612+
// If the variable is immutable, it's fine so long as it involves
1613+
// ConcurrentValue types.
1614+
//
1615+
// When flow-sensitive concurrent captures are enabled, we also
1616+
// allow reads, depending on a SIL diagnostic pass to identify the
1617+
// remaining race conditions.
1618+
if (!var->supportsMutation() ||
1619+
(ctx.LangOpts.EnableExperimentalFlowSensitiveConcurrentCaptures &&
1620+
parent.dyn_cast<LoadExpr *>())) {
16151621
return diagnoseNonConcurrentTypesInReference(
16161622
valueRef, getDeclContext(), loc,
16171623
ConcurrentReferenceKind::LocalCapture);
16181624
}
16191625

1620-
// Otherwise, we have concurrent mutation. Complain.
1626+
// Otherwise, we have concurrent access. Complain.
16211627
ctx.Diags.diagnose(
1622-
loc, diag::concurrent_mutation_of_local_capture,
1628+
loc, diag::concurrent_access_of_local_capture,
1629+
parent.dyn_cast<LoadExpr *>(),
16231630
var->getDescriptiveKind(), var->getName());
16241631
return true;
16251632
}
@@ -2307,10 +2314,12 @@ void swift::checkConcurrentValueConformance(ProtocolConformance *conformance) {
23072314
if (!nominal)
23082315
return;
23092316

2310-
// Actors implicitly conform to ConcurrentValue and protect their state.
23112317
auto classDecl = dyn_cast<ClassDecl>(nominal);
2312-
if (classDecl && classDecl->isActor())
2313-
return;
2318+
if (classDecl) {
2319+
// Actors implicitly conform to ConcurrentValue and protect their state.
2320+
if (classDecl->isActor())
2321+
return;
2322+
}
23142323

23152324
// ConcurrentValue can only be used in the same source file.
23162325
auto conformanceDecl = conformanceDC->getAsDecl();
@@ -2322,6 +2331,29 @@ void swift::checkConcurrentValueConformance(ProtocolConformance *conformance) {
23222331
return;
23232332
}
23242333

2334+
if (classDecl) {
2335+
// An open class cannot conform to `ConcurrentValue`.
2336+
if (classDecl->getFormalAccess() == AccessLevel::Open) {
2337+
classDecl->diagnose(
2338+
diag::concurrent_value_open_class, classDecl->getName());
2339+
return;
2340+
}
2341+
2342+
// A 'ConcurrentValue' class cannot inherit from another class, although
2343+
// we allow `NSObject` for Objective-C interoperability.
2344+
if (!isa<InheritedProtocolConformance>(conformance)) {
2345+
if (auto superclassDecl = classDecl->getSuperclassDecl()) {
2346+
if (!superclassDecl->isNSObject()) {
2347+
classDecl->diagnose(
2348+
diag::concurrent_value_inherit,
2349+
nominal->getASTContext().LangOpts.EnableObjCInterop,
2350+
classDecl->getName());
2351+
return;
2352+
}
2353+
}
2354+
}
2355+
}
2356+
23252357
// Stored properties of structs and classes must have
23262358
// ConcurrentValue-conforming types.
23272359
if (isa<StructDecl>(nominal) || classDecl) {

test/Concurrency/Runtime/actor_counters.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
6161
var workers: [Task.Handle<Void>] = []
6262
for i in 0..<numWorkers {
6363
workers.append(
64-
Task.runDetached {
64+
Task.runDetached { [counters] in
6565
usleep(UInt32.random(in: 0..<100) * 1000)
6666
await worker(
6767
identity: i, counters: counters, numIterations: numIterations,

test/Concurrency/actor_isolation.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ extension MyActor {
142142
acceptConcurrentClosure {
143143
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent closure}}
144144
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
145-
_ = localVar // okay
145+
_ = localVar // expected-error{{reference to captured var 'localVar' in concurrently-executing code}}
146146
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
147147
_ = localConstant
148148
}
@@ -159,7 +159,7 @@ extension MyActor {
159159
@concurrent func localFn1() {
160160
_ = self.text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
161161
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
162-
_ = localVar // okay
162+
_ = localVar // expected-error{{reference to captured var 'localVar' in concurrently-executing code}}
163163
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
164164
_ = localConstant
165165
}
@@ -168,7 +168,7 @@ extension MyActor {
168168
acceptClosure {
169169
_ = text[0] // expected-error{{actor-isolated property 'text' cannot be referenced from a concurrent function}}
170170
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent function}}
171-
_ = localVar // okay
171+
_ = localVar // expected-error{{reference to captured var 'localVar' in concurrently-executing code}}
172172
localVar = 25 // expected-error{{mutation of captured var 'localVar' in concurrently-executing code}}
173173
_ = localConstant
174174
}
@@ -324,14 +324,18 @@ func testGlobalRestrictions(actor: MyActor) async {
324324
// Global mutable state cannot be accessed.
325325
_ = mutableGlobal // expected-warning{{reference to var 'mutableGlobal' is not concurrency-safe because it involves shared mutable state}}
326326

327-
// Local mutable variables cannot be modified from concurrently-executing
327+
// Local mutable variables cannot be accessed from concurrently-executing
328328
// code.
329329
var i = 17
330330
acceptConcurrentClosure {
331-
_ = i
331+
_ = i // expected-error{{reference to captured var 'i' in concurrently-executing code}}
332332
i = 42 // expected-error{{mutation of captured var 'i' in concurrently-executing code}}
333333
}
334334
print(i)
335+
336+
acceptConcurrentClosure { [i] in
337+
_ = i
338+
}
335339
}
336340

337341
func f() {

test/Concurrency/concurrent_value_checking.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func testConcurrency() {
118118
}
119119
acceptConcurrent {
120120
print(x) // expected-warning{{cannot use let 'x' with a non-concurrent-value type 'NotConcurrent' from concurrently-executed code}}
121-
print(y) // expected-warning{{cannot use var 'y' with a non-concurrent-value type 'NotConcurrent' from concurrently-executed code}}
121+
print(y) // expected-error{{reference to captured var 'y' in concurrently-executing code}}
122122
}
123123
}
124124

@@ -147,8 +147,7 @@ class SomeClass: MainActorProto {
147147
// ConcurrentValue restriction on concurrent functions.
148148
// ----------------------------------------------------------------------
149149

150-
// FIXME: poor diagnostic
151-
@concurrent func concurrentFunc() -> NotConcurrent? { nil } // expected-warning{{cannot call function returning non-concurrent-value type 'NotConcurrent?' across actors}}
150+
@concurrent func concurrentFunc() -> NotConcurrent? { nil }
152151

153152
// ----------------------------------------------------------------------
154153
// No ConcurrentValue restriction on @concurrent function types.
@@ -222,9 +221,20 @@ class C5: UnsafeConcurrentValue {
222221
}
223222

224223
class C6: C5 {
225-
var y: Int = 0 // still okay
224+
var y: Int = 0 // still okay, it's unsafe
226225
}
227226

227+
class C7<T>: ConcurrentValue { }
228+
229+
class C8: C7<Int> { } // okay
230+
231+
open class C9: ConcurrentValue { } // expected-error{{open class 'C9' cannot conform to `ConcurrentValue`; use `UnsafeConcurrentValue`}}
232+
233+
public class C10: ConcurrentValue { }
234+
// expected-note@-1{{superclass is declared here}}
235+
open class C11: C10 { }
236+
// expected-error@-1{{superclass 'C10' of open class must be open}}
237+
// expected-error@-2{{open class 'C11' cannot conform to `ConcurrentValue`; use `UnsafeConcurrentValue`}}
228238

229239
// ----------------------------------------------------------------------
230240
// UnsafeConcurrentValue disabling checking
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -enable-experimental-concurrent-value-checking
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: objc_interop
5+
6+
import Foundation
7+
8+
class A: NSObject, ConcurrentValue {
9+
let x: Int = 5
10+
}
11+
12+
class B: NSObject, ConcurrentValue {
13+
var x: Int = 5 // expected-error{{stored property 'x' of 'ConcurrentValue'-conforming class 'B' is mutable}}
14+
}
15+
16+
class C { }
17+
18+
class D: NSObject, ConcurrentValue {
19+
let c: C = C() // expected-error{{stored property 'c' of 'ConcurrentValue'-conforming class 'D' has non-concurrent-value type 'C'}}
20+
}
21+
22+

test/Concurrency/concurrentfunction_capturediagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -enable-experimental-concurrency -verify -emit-sil %s -o - >/dev/null
1+
// RUN: %target-swift-frontend -enable-experimental-concurrency -enable-experimental-flow-sensitive-concurrent-captures -verify -emit-sil %s -o - >/dev/null
22

33
// REQUIRES: concurrency
44

test/SILGen/concurrent_functions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency | %FileCheck %s
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 -enable-experimental-concurrency -enable-experimental-flow-sensitive-concurrent-captures | %FileCheck %s
22
// REQUIRES: concurrency
33

44
func acceptsConcurrent(_: @escaping @concurrent () -> Int) { }

test/attr/attr_concurrent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency
1+
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency -enable-experimental-flow-sensitive-concurrent-captures
22
// REQUIRES: concurrency
33

44
// Concurrent attribute on a function type.

0 commit comments

Comments
 (0)