Skip to content

Commit 46f167b

Browse files
authored
Merge pull request swiftlang#36644 from DougGregor/further-global-actor-inference
[Actor inference] Extend global actor inference
2 parents e660b70 + 5342706 commit 46f167b

File tree

4 files changed

+155
-41
lines changed

4 files changed

+155
-41
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,38 @@ static Optional<ActorIsolation> getIsolationFromWitnessedRequirements(
24982498
return std::get<1>(isolatedRequirements.front());
24992499
}
25002500

2501+
/// Compute the isolation of a nominal type from the conformances that
2502+
/// are directly specified on the type.
2503+
static Optional<ActorIsolation> getIsolationFromConformances(
2504+
NominalTypeDecl *nominal) {
2505+
if (isa<ProtocolDecl>(nominal))
2506+
return None;
2507+
2508+
Optional<ActorIsolation> foundIsolation;
2509+
for (auto proto : nominal->getLocalProtocols()) {
2510+
switch (auto protoIsolation = getActorIsolation(proto)) {
2511+
case ActorIsolation::ActorInstance:
2512+
case ActorIsolation::Unspecified:
2513+
case ActorIsolation::Independent:
2514+
break;
2515+
2516+
case ActorIsolation::GlobalActor:
2517+
case ActorIsolation::GlobalActorUnsafe:
2518+
if (!foundIsolation) {
2519+
foundIsolation = protoIsolation;
2520+
continue;
2521+
}
2522+
2523+
if (*foundIsolation != protoIsolation)
2524+
return None;
2525+
2526+
break;
2527+
}
2528+
}
2529+
2530+
return foundIsolation;
2531+
}
2532+
25012533
// Check whether a declaration is an asynchronous handler.
25022534
static bool isAsyncHandler(ValueDecl *value) {
25032535
if (auto func = dyn_cast<AbstractFunctionDecl>(value)) {
@@ -2508,6 +2540,61 @@ static bool isAsyncHandler(ValueDecl *value) {
25082540
return false;
25092541
}
25102542

2543+
namespace {
2544+
2545+
/// Describes how actor isolation is propagated to a member, if at all.
2546+
enum class MemberIsolationPropagation {
2547+
GlobalActor,
2548+
AnyIsolation
2549+
};
2550+
2551+
}
2552+
/// Determine how the given member can receive its isolation from its type
2553+
/// context.
2554+
static Optional<MemberIsolationPropagation> getMemberIsolationPropagation(
2555+
const ValueDecl *value) {
2556+
if (!value->getDeclContext()->isTypeContext())
2557+
return None;
2558+
2559+
switch (value->getKind()) {
2560+
case DeclKind::Import:
2561+
case DeclKind::Extension:
2562+
case DeclKind::TopLevelCode:
2563+
case DeclKind::InfixOperator:
2564+
case DeclKind::PrefixOperator:
2565+
case DeclKind::PostfixOperator:
2566+
case DeclKind::IfConfig:
2567+
case DeclKind::PoundDiagnostic:
2568+
case DeclKind::PrecedenceGroup:
2569+
case DeclKind::MissingMember:
2570+
case DeclKind::Class:
2571+
case DeclKind::Enum:
2572+
case DeclKind::Protocol:
2573+
case DeclKind::Struct:
2574+
case DeclKind::TypeAlias:
2575+
case DeclKind::GenericTypeParam:
2576+
case DeclKind::AssociatedType:
2577+
case DeclKind::OpaqueType:
2578+
case DeclKind::Param:
2579+
case DeclKind::Module:
2580+
return None;
2581+
2582+
case DeclKind::PatternBinding:
2583+
case DeclKind::EnumCase:
2584+
case DeclKind::EnumElement:
2585+
case DeclKind::Constructor:
2586+
case DeclKind::Destructor:
2587+
return MemberIsolationPropagation::GlobalActor;
2588+
2589+
case DeclKind::Func:
2590+
case DeclKind::Accessor:
2591+
case DeclKind::Subscript:
2592+
case DeclKind::Var:
2593+
return value->isInstanceMember() ? MemberIsolationPropagation::AnyIsolation
2594+
: MemberIsolationPropagation::GlobalActor;
2595+
}
2596+
}
2597+
25112598
ActorIsolation ActorIsolationRequest::evaluate(
25122599
Evaluator &evaluator, ValueDecl *value) const {
25132600
// If this declaration has one of the actor isolation attributes, report
@@ -2537,12 +2624,16 @@ ActorIsolation ActorIsolationRequest::evaluate(
25372624
}
25382625

25392626
// Function used when returning an inferred isolation.
2540-
auto inferredIsolation = [&](ActorIsolation inferred) {
2627+
auto inferredIsolation = [&](
2628+
ActorIsolation inferred, bool onlyGlobal = false) {
25412629
// Add an implicit attribute to capture the actor isolation that was
25422630
// inferred, so that (e.g.) it will be printed and serialized.
25432631
ASTContext &ctx = value->getASTContext();
25442632
switch (inferred) {
25452633
case ActorIsolation::Independent:
2634+
if (onlyGlobal)
2635+
return ActorIsolation::forUnspecified();
2636+
25462637
value->getAttrs().add(new (ctx) ActorIndependentAttr(
25472638
ActorIndependentKind::Safe, /*IsImplicit=*/true));
25482639
break;
@@ -2560,6 +2651,9 @@ ActorIsolation ActorIsolationRequest::evaluate(
25602651

25612652
case ActorIsolation::ActorInstance:
25622653
case ActorIsolation::Unspecified:
2654+
if (onlyGlobal)
2655+
return ActorIsolation::forUnspecified();
2656+
25632657
// Nothing to do.
25642658
break;
25652659
}
@@ -2659,31 +2753,35 @@ ActorIsolation ActorIsolationRequest::evaluate(
26592753
}
26602754
}
26612755
}
2756+
2757+
// If the declaration is a nominal type and any of the protocols to which
2758+
// it directly conforms is isolated to a global actor, use that.
2759+
if (auto nominal = dyn_cast<NominalTypeDecl>(value)) {
2760+
if (auto conformanceIsolation = getIsolationFromConformances(nominal))
2761+
if (auto inferred = inferredIsolation(*conformanceIsolation))
2762+
return inferred;
2763+
}
26622764
}
26632765

2664-
// Instance members infer isolation from their context.
2665-
if (value->isInstanceMember()) {
2766+
// Infer isolation for a member.
2767+
if (auto memberPropagation = getMemberIsolationPropagation(value)) {
2768+
// If were only allowed to propagate global actors, do so.
2769+
bool onlyGlobal =
2770+
*memberPropagation == MemberIsolationPropagation::GlobalActor;
2771+
26662772
// If the declaration is in an extension that has one of the isolation
26672773
// attributes, use that.
26682774
if (auto ext = dyn_cast<ExtensionDecl>(value->getDeclContext())) {
26692775
if (auto isolationFromAttr = getIsolationFromAttributes(ext)) {
2670-
return inferredIsolation(*isolationFromAttr);
2776+
return inferredIsolation(*isolationFromAttr, onlyGlobal);
26712777
}
26722778
}
26732779

26742780
// If the declaration is in a nominal type (or extension thereof) that
26752781
// has isolation, use that.
26762782
if (auto selfTypeDecl = value->getDeclContext()->getSelfNominalTypeDecl()) {
2677-
if (auto selfTypeIsolation = getActorIsolation(selfTypeDecl)) {
2678-
// Don't propagate implicit, unsafe global actor isolation from a type
2679-
// to non-imported instance members.
2680-
if (selfTypeIsolation != ActorIsolation::GlobalActorUnsafe ||
2681-
value->getClangNode() ||
2682-
(selfTypeDecl->getGlobalActorAttr() &&
2683-
!selfTypeDecl->getGlobalActorAttr()->first->isImplicit())) {
2684-
return inferredIsolation(selfTypeIsolation);
2685-
}
2686-
}
2783+
if (auto selfTypeIsolation = getActorIsolation(selfTypeDecl))
2784+
return inferredIsolation(selfTypeIsolation, onlyGlobal);
26872785
}
26882786
}
26892787

test/ClangImporter/objc_async.swift

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import Foundation
66
import ObjCConcurrency
77

8-
@MainActor func onlyOnMainActor() { } // expected-note{{calls to global function 'onlyOnMainActor()' from outside of its actor context are implicitly asynchronous}}
8+
@MainActor func onlyOnMainActor() { }
99

1010
func testSlowServer(slowServer: SlowServer) async throws {
1111
let _: Int = await slowServer.doSomethingSlow("mail")
@@ -130,8 +130,6 @@ struct SomeGlobalActor {
130130
static let shared = SomeActor()
131131
}
132132

133-
@SomeGlobalActor(unsafe) func unsafelyOnSomeGlobal() { }
134-
135133
class MyButton : NXButton {
136134
@MainActor func testMain() {
137135
onButtonPress() // okay
@@ -141,29 +139,11 @@ class MyButton : NXButton {
141139
onButtonPress() // expected-error{{instance method 'onButtonPress()' isolated to global actor 'MainActor' can not be referenced from different global actor 'SomeGlobalActor'}}
142140
}
143141

144-
func test() { // expected-note{{add '@MainActor' to make instance method 'test()' part of global actor 'MainActor'}}
145-
onButtonPress() // okay, onButtonPress is @MainActor(unsafe)
146-
unsafelyOnSomeGlobal() // okay, we haven't opted into anything
147-
onlyOnMainActor() // expected-error{{global function 'onlyOnMainActor()' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
148-
}
149-
}
150-
151-
class MyOtherButton: NXButton {
152-
override func onButtonPress() { // expected-note{{calls to instance method 'onButtonPress()' from outside of its actor context are implicitly asynchronous}}
153-
onlyOnMainActor() // yes, we're on the main actor
154-
unsafelyOnSomeGlobal() // okay, we haven't opted into any actual checking
155-
}
156-
157142
func test() {
158-
onButtonPress() // okay, it's @MainActor(unsafe)
159-
}
160-
161-
@SomeGlobalActor func testOther() {
162-
onButtonPress() // expected-error{{instance method 'onButtonPress()' isolated to global actor 'MainActor' can not be referenced from different global actor 'SomeGlobalActor' in a synchronous context}}
143+
onButtonPress() // okay
163144
}
164145
}
165146

166-
167147
func testButtons(mb: MyButton) {
168148
mb.onButtonPress()
169149
}

test/Concurrency/actor_isolation.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -647,14 +647,24 @@ class SomeClassWithInits {
647647
var mutableState: Int = 17
648648
var otherMutableState: Int
649649

650-
init() {
650+
static var shared = SomeClassWithInits() // expected-note{{static property declared here}}
651+
652+
init() { // expected-note{{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
651653
self.mutableState = 42
652654
self.otherMutableState = 17
653655

654-
self.isolated() // expected-error{{'isolated()' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
656+
self.isolated()
657+
}
658+
659+
deinit {
660+
print(SomeClassWithInits.shared) // okay, we're actor-isolated
655661
}
656662

657-
func isolated() { } // expected-note{{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
663+
func isolated() { }
664+
665+
static func staticIsolated() { // expected-note{{calls to static method 'staticIsolated()' from outside of its actor context are implicitly asynchronous}}
666+
_ = SomeClassWithInits.shared
667+
}
658668

659669
func hasDetached() {
660670
Task.runDetached {
@@ -668,8 +678,10 @@ class SomeClassWithInits {
668678
}
669679
}
670680

671-
func outsideSomeClassWithInits() {
672-
_ = SomeClassWithInits() // okay, initializer is not isolated
681+
func outsideSomeClassWithInits() { // expected-note 3 {{add '@MainActor' to make global function 'outsideSomeClassWithInits()' part of global actor 'MainActor'}}
682+
_ = SomeClassWithInits() // expected-error{{initializer 'init()' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
683+
_ = SomeClassWithInits.shared // expected-error{{static property 'shared' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
684+
SomeClassWithInits.staticIsolated() // expected-error{{static method 'staticIsolated()' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
673685
}
674686

675687
// ----------------------------------------------------------------------

test/Concurrency/global_actor_inference.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ class C2: P2 {
6868
}
6969
}
7070

71+
struct AllInP1: P1 {
72+
func method() { } // expected-note {{calls to instance method 'method()' from outside of its actor context are implicitly asynchronous}}
73+
func other() { } // expected-note {{calls to instance method 'other()' from outside of its actor context are implicitly asynchronous}}
74+
}
75+
76+
func testAllInP1(ap1: AllInP1) { // expected-note 2 {{add '@SomeGlobalActor' to make global function 'testAllInP1(ap1:)' part of global actor 'SomeGlobalActor'}}
77+
ap1.method() // expected-error{{instance method 'method()' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
78+
ap1.other() // expected-error{{instance method 'other()' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
79+
}
80+
81+
struct NotAllInP1 {
82+
func other() { }
83+
}
84+
85+
extension NotAllInP1: P1 {
86+
func method() { } // expected-note{{calls to instance method 'method()' from outside of its actor context are implicitly asynchronous}}
87+
}
88+
89+
func testNotAllInP1(nap1: NotAllInP1) { // expected-note{{add '@SomeGlobalActor' to make global function 'testNotAllInP1(nap1:)' part of global actor 'SomeGlobalActor'}}
90+
nap1.method() // expected-error{{instance method 'method()' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
91+
nap1.other() // okay
92+
}
93+
94+
7195
// ----------------------------------------------------------------------
7296
// Global actor inference for classes and extensions
7397
// ----------------------------------------------------------------------

0 commit comments

Comments
 (0)