Skip to content

Commit b412a44

Browse files
committed
Allow cross-actor calls to synchronous global actor functions.
... and add some tests I've had lying around locally.
1 parent 2f2c194 commit b412a44

File tree

4 files changed

+128
-45
lines changed

4 files changed

+128
-45
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,61 +1724,36 @@ namespace {
17241724
}
17251725

17261726
case ActorIsolation::Unspecified: {
1727-
// NOTE: we must always inspect for implicitlyAsync
17281727
auto result = tryMarkImplicitlyAsync(loc, valueRef, context);
1729-
bool implicitlyAsyncExpr = (result == AsyncMarkingResult::FoundAsync);
1730-
bool didEmitDiagnostic = false;
1728+
if (result == AsyncMarkingResult::FoundAsync)
1729+
return false;
17311730

1732-
auto emitError = [&](bool justNote = false) {
1733-
didEmitDiagnostic = true;
1734-
if (!justNote) {
1735-
auto useKind = static_cast<unsigned>(
1736-
kindOfUsage(value, context).getValueOr(VarRefUseEnv::Read));
1737-
ctx.Diags.diagnose(
1738-
loc, diag::global_actor_from_nonactor_context,
1739-
value->getDescriptiveKind(), value->getName(), globalActor,
1740-
/*actorIndependent=*/false, useKind,
1741-
result == AsyncMarkingResult::SyncContext);
1742-
}
1743-
noteIsolatedActorMember(value, context);
1744-
};
1745-
1746-
if (AbstractFunctionDecl const* fn =
1747-
dyn_cast_or_null<AbstractFunctionDecl>(declContext->getAsDecl())) {
1748-
bool isAsyncContext = fn->isAsyncContext();
1749-
1750-
if (implicitlyAsyncExpr && isAsyncContext)
1751-
return didEmitDiagnostic; // definitely an OK reference.
1752-
1753-
// otherwise, there's something wrong.
1754-
1755-
// if it's an implicitly-async call in a non-async context,
1756-
// then we know later type-checking will raise an error,
1757-
// so we just emit a note pointing out that callee of the call is
1758-
// implicitly async.
1759-
emitError(/*justNote=*/implicitlyAsyncExpr);
1760-
1761-
// otherwise, if it's any kind of global-actor reference within
1762-
// this synchronous function, we'll additionally suggest becoming
1763-
// part of the global actor associated with the reference,
1764-
// since this function is not associated with an actor.
1765-
if (isa<FuncDecl>(fn) && !isAsyncContext) {
1766-
didEmitDiagnostic = true;
1767-
fn->diagnose(diag::note_add_globalactor_to_function,
1731+
// Diagnose the reference.
1732+
auto useKind = static_cast<unsigned>(
1733+
kindOfUsage(value, context).getValueOr(VarRefUseEnv::Read));
1734+
ctx.Diags.diagnose(
1735+
loc, diag::global_actor_from_nonactor_context,
1736+
value->getDescriptiveKind(), value->getName(), globalActor,
1737+
/*actorIndependent=*/false, useKind,
1738+
result == AsyncMarkingResult::SyncContext);
1739+
1740+
// If we are in a synchronous function on the global actor,
1741+
// suggest annotating with the global actor itself.
1742+
if (auto fn = dyn_cast<FuncDecl>(declContext)) {
1743+
if (!isa<AccessorDecl>(fn) && !fn->isAsyncContext()) {
1744+
fn->diagnose(diag::note_add_globalactor_to_function,
17681745
globalActor->getWithoutParens().getString(),
17691746
fn->getDescriptiveKind(),
17701747
fn->getName(),
17711748
globalActor)
1772-
.fixItInsert(fn->getAttributeInsertionLoc(false),
1749+
.fixItInsert(fn->getAttributeInsertionLoc(false),
17731750
diag::insert_globalactor_attr, globalActor);
17741751
}
1775-
1776-
} else {
1777-
// just the generic error with note.
1778-
emitError();
17791752
}
17801753

1781-
return didEmitDiagnostic;
1754+
noteIsolatedActorMember(value, context);
1755+
1756+
return true;
17821757
} // end Unspecified case
17831758
} // end switch
17841759
llvm_unreachable("unhandled actor isolation kind!");

test/Concurrency/actor_isolation.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,4 +655,15 @@ class SomeClassWithInits {
655655
}
656656

657657
func isolated() { }
658+
659+
func hasDetached() {
660+
Task.runDetached {
661+
// okay
662+
await self.isolated() // expected-warning{{cannot use parameter 'self' with a non-concurrent-value type 'SomeClassWithInits' from concurrently-executed code}}
663+
self.isolated() // expected-warning{{cannot use parameter 'self' with a non-concurrent-value type 'SomeClassWithInits' from concurrently-executed code}}
664+
// expected-error@-1{{call is 'async' but is not marked with 'await'}}
665+
666+
print(await self.mutableState) // expected-warning{{cannot use parameter 'self' with a non-concurrent-value type 'SomeClassWithInits' from concurrently-executed code}}
667+
}
668+
}
658669
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
2+
3+
@globalActor
4+
actor SomeGlobalActor {
5+
static let shared = SomeGlobalActor()
6+
}
7+
8+
@MainActor(unsafe) func globalMain() { } // expected-note {{calls to global function 'globalMain()' from outside of its actor context are implicitly asynchronous}}
9+
10+
@SomeGlobalActor(unsafe) func globalSome() { } // expected-note 2{{calls to global function 'globalSome()' from outside of its actor context are implicitly asynchronous}}
11+
12+
// ----------------------------------------------------------------------
13+
// Witnessing and unsafe global actor
14+
// ----------------------------------------------------------------------
15+
protocol P1 {
16+
@MainActor(unsafe) func onMainActor() // expected-note{{'onMainActor()' declared here}}
17+
}
18+
19+
struct S1_P1: P1 {
20+
func onMainActor() { }
21+
}
22+
23+
struct S2_P1: P1 {
24+
@MainActor func onMainActor() { }
25+
}
26+
27+
struct S3_P1: P1 {
28+
@actorIndependent func onMainActor() { }
29+
}
30+
31+
struct S4_P1: P1 {
32+
@SomeGlobalActor func onMainActor() { } // expected-error{{instance method 'onMainActor()' isolated to global actor 'SomeGlobalActor' can not satisfy corresponding requirement from protocol 'P1' isolated to global actor 'MainActor'}}
33+
}
34+
35+
// ----------------------------------------------------------------------
36+
// Overriding and unsafe global actor
37+
// ----------------------------------------------------------------------
38+
class C1 {
39+
@MainActor(unsafe) func method() { } // expected-note{{overridden declaration is here}}
40+
}
41+
42+
class C2: C1 {
43+
override func method() { // expected-note{{overridden declaration is here}}
44+
globalSome() // okay
45+
}
46+
}
47+
48+
class C3: C1 {
49+
@actorIndependent override func method() {
50+
globalSome() // expected-error{{global function 'globalSome()' isolated to global actor 'SomeGlobalActor' can not be referenced from an '@actorIndependent' synchronous context}}
51+
}
52+
}
53+
54+
class C4: C1 {
55+
@MainActor override func method() {
56+
globalSome() // expected-error{{global function 'globalSome()' isolated to global actor 'SomeGlobalActor' can not be referenced from different global actor 'MainActor'}}
57+
}
58+
}
59+
60+
class C5: C1 {
61+
@SomeGlobalActor override func method() { // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'method()' has different actor isolation from global actor 'MainActor'-isolated overridden declaration}}
62+
}
63+
}
64+
65+
class C6: C2 {
66+
// We didn't infer any actor isolation for C2.method().
67+
@SomeGlobalActor override func method() { // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'method()' has different actor isolation from non-actor-isolated overridden declaration}}
68+
}
69+
}
70+
71+
class C7: C2 {
72+
// We didn't infer any actor isolation for C2.method(), but it's okay to be
73+
// explicitly unsafe.
74+
@SomeGlobalActor(unsafe) override func method() {
75+
globalMain() // expected-error{{global function 'globalMain()' isolated to global actor 'MainActor' can not be referenced from different global actor 'SomeGlobalActor'}}
76+
globalSome() // okay
77+
}
78+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %target-typecheck-verify-swift -warn-concurrency
2+
// REQUIRES: concurrency
3+
4+
class GlobalCounter {
5+
var counter: Int = 0
6+
}
7+
8+
let rs = GlobalCounter()
9+
var globalInt = 17 // expected-note 2{{var declared here}}
10+
11+
class MyError: Error { // expected-warning{{non-final class 'MyError' cannot conform to `ConcurrentValue`; use `UnsafeConcurrentValue`}}
12+
var storage = 0 // expected-warning{{stored property 'storage' of 'ConcurrentValue'-conforming class 'MyError' is mutable}}
13+
}
14+
15+
func testWarnings() {
16+
_ = rs // TODO: warn here
17+
_ = globalInt // expected-warning{{reference to var 'globalInt' is not concurrency-safe because it involves shared mutable state}}
18+
globalInt += 1 // expected-warning{{reference to var 'globalInt' is not concurrency-safe because it involves shared mutable state}}
19+
}

0 commit comments

Comments
 (0)