Skip to content

Commit e133dad

Browse files
committed
[Distributed] Implement isolation check aware of Task/Task.detached etc
1 parent 028b964 commit e133dad

8 files changed

+265
-133
lines changed

include/swift/AST/DiagnosticsSema.def

+8-2
Original file line numberDiff line numberDiff line change
@@ -4329,6 +4329,12 @@ NOTE(note_add_async_to_function,none,
43294329
NOTE(note_add_nonisolated_to_decl,none,
43304330
"add 'nonisolated' to %0 to make this %1 not isolated to the actor",
43314331
(DeclName, DescriptiveDeclKind))
4332+
NOTE(note_add_distributed_to_decl,none,
4333+
"add 'distributed' to %0 to make this %1 witness the protocol requirement",
4334+
(DeclName, DescriptiveDeclKind))
4335+
NOTE(note_distributed_requirement_defined_here,none,
4336+
"distributed function requirement %0 declared here",
4337+
(DeclName))
43324338
NOTE(note_add_globalactor_to_function,none,
43334339
"add '@%0' to make %1 %2 part of global actor %3",
43344340
(StringRef, DescriptiveDeclKind, DeclName, Type))
@@ -4398,8 +4404,8 @@ ERROR(actor_isolated_non_self_reference,none,
43984404
"from the main actor|from a non-isolated context}3",
43994405
(DescriptiveDeclKind, DeclName, unsigned, unsigned, Type))
44004406
ERROR(distributed_actor_isolated_non_self_reference,none,
4401-
"distributed actor-isolated %0 %1 can only be referenced "
4402-
"inside the distributed actor",
4407+
"distributed actor-isolated %0 %1 can only be referenced inside the "
4408+
"distributed actor",
44034409
(DescriptiveDeclKind, DeclName))
44044410
ERROR(distributed_actor_needs_explicit_distributed_import,none,
44054411
"'_Distributed' module not imported, required for 'distributed actor'",

lib/Sema/TypeCheckConcurrency.cpp

+129-94
Large diffs are not rendered by default.

lib/Sema/TypeCheckConcurrency.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class ActorIsolationRestriction {
118118

119119
/// References to declarations that are part of a distributed actor are
120120
/// only permitted if they are async.
121-
DistributedActorSelf,
121+
CrossDistributedActorSelf, // FIXME(distributed): remove this case entirely rdar://83713366
122122
};
123123

124124
private:
@@ -150,7 +150,7 @@ class ActorIsolationRestriction {
150150
NominalTypeDecl *getActorType() const {
151151
assert(kind == ActorSelf ||
152152
kind == CrossActorSelf ||
153-
kind == DistributedActorSelf);
153+
kind == CrossDistributedActorSelf);
154154
return data.actorType;
155155
}
156156

@@ -174,6 +174,7 @@ class ActorIsolationRestriction {
174174
/// the current actor or is a cross-actor access.
175175
static ActorIsolationRestriction forActorSelf(
176176
NominalTypeDecl *actor, bool isCrossActor) {
177+
177178
ActorIsolationRestriction result(isCrossActor? CrossActorSelf : ActorSelf,
178179
isCrossActor);
179180
result.data.actorType = actor;
@@ -184,7 +185,8 @@ class ActorIsolationRestriction {
184185
/// the current actor.
185186
static ActorIsolationRestriction forDistributedActorSelf(
186187
NominalTypeDecl *actor, bool isCrossActor) {
187-
ActorIsolationRestriction result(DistributedActorSelf, isCrossActor);
188+
ActorIsolationRestriction result(isCrossActor ? CrossActorSelf : ActorSelf,
189+
isCrossActor);
188190
result.data.actorType = actor;
189191
return result;
190192
}

lib/Sema/TypeCheckDeclObjC.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ static bool checkObjCActorIsolation(const ValueDecl *VD,
455455
case ActorIsolationRestriction::GlobalActor:
456456
// FIXME: Consider whether to limit @objc on global-actor-qualified
457457
// declarations.
458-
case ActorIsolationRestriction::DistributedActorSelf:
458+
case ActorIsolationRestriction::CrossDistributedActorSelf:
459459
// we do not allow distributed + objc actors.
460460
return false;
461461
case ActorIsolationRestriction::Unrestricted:

lib/Sema/TypeCheckProtocol.cpp

+42-23
Original file line numberDiff line numberDiff line change
@@ -2824,24 +2824,6 @@ bool ConformanceChecker::checkActorIsolation(
28242824
ActorIsolationRestriction::forDeclaration(
28252825
witness, witness->getDeclContext(),
28262826
/*fromExpression=*/false)) {
2827-
case ActorIsolationRestriction::DistributedActorSelf: {
2828-
if (witness->isSynthesized()) {
2829-
// we only have two 'distributed-actor-nonisolated' properties,
2830-
// the address and transport; if we see any such marked property,
2831-
// we're free to automatically assume those are fine and accessible always.
2832-
if (witness->isDistributedActorIndependent())
2833-
return false;
2834-
}
2835-
2836-
if (auto funcDecl = dyn_cast<AbstractFunctionDecl>(witness)) {
2837-
// A 'distributed func' may witness a distributed isolated function requirement.
2838-
if (funcDecl->isDistributed())
2839-
return false;
2840-
}
2841-
2842-
// continue checking ActorSelf rules
2843-
LLVM_FALLTHROUGH;
2844-
}
28452827
case ActorIsolationRestriction::ActorSelf: {
28462828
auto requirementIsolation = getActorIsolation(requirement);
28472829

@@ -2850,23 +2832,60 @@ bool ConformanceChecker::checkActorIsolation(
28502832
if (requirementIsolation == ActorIsolation::ActorInstance)
28512833
return false;
28522834

2835+
auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness);
2836+
auto requirementFunc = dyn_cast<AbstractFunctionDecl>(requirement);
2837+
2838+
/// Distributed actors can witness protocol requirements either with
2839+
/// nonisolated or distributed members.
2840+
auto witnessClass = dyn_cast<ClassDecl>(witness->getDeclContext());
2841+
if (witnessClass && witnessClass->isDistributedActor()) {
2842+
// we only have two 'distributed-actor-nonisolated' properties,
2843+
// the address and transport; if we see any such marked property,
2844+
// we're free to automatically assume those are fine and accessible always.
2845+
if (witness->isSynthesized() && witness->isDistributedActorIndependent()) {
2846+
return false;
2847+
}
2848+
2849+
// Maybe we're dealing with a 'distributed func' which is witness to
2850+
// a distributed function requirement, this is ok.
2851+
if (requirementFunc && requirementFunc->isDistributed() &&
2852+
witnessFunc && witnessFunc->isDistributed()) {
2853+
return false;
2854+
}
2855+
}
2856+
28532857
witness->diagnose(diag::actor_isolated_witness,
28542858
witness->getDescriptiveKind(),
28552859
witness->getName());
28562860
{
28572861
auto witnessVar = dyn_cast<VarDecl>(witness);
28582862
if ((witnessVar && !witnessVar->hasStorage()) || !witnessVar) {
2859-
auto independentNote = witness->diagnose(
2860-
diag::note_add_nonisolated_to_decl,
2861-
witness->getName(), witness->getDescriptiveKind());
2862-
independentNote.fixItInsert(witness->getAttributeInsertionLoc(true),
2863-
"nonisolated ");
2863+
if (requirementFunc && requirementFunc->isDistributed()) {
2864+
// a distributed protocol requirement can be witnessed with a
2865+
// distributed function:
2866+
witness
2867+
->diagnose(diag::note_add_distributed_to_decl,
2868+
witness->getName(), witness->getDescriptiveKind())
2869+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2870+
"distributed ");
2871+
requirement
2872+
->diagnose(diag::note_distributed_requirement_defined_here,
2873+
requirement->getName());
2874+
} else {
2875+
// the only other way to witness is to use a nonisolated member:
2876+
witness
2877+
->diagnose(diag::note_add_nonisolated_to_decl, witness->getName(),
2878+
witness->getDescriptiveKind())
2879+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2880+
"nonisolated ");
2881+
}
28642882
}
28652883
}
28662884

28672885
return true;
28682886
}
28692887

2888+
case ActorIsolationRestriction::CrossDistributedActorSelf:
28702889
case ActorIsolationRestriction::CrossActorSelf:
28712890
return diagnoseNonSendableTypesInReference(
28722891
witness, DC->getParentModule(), witness->getLoc(),

test/Distributed/distributed_actor_isolation.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func test_outside(
142142
_ = DistributedActor_1.staticFunc()
143143

144144
// ==== non-distributed functions
145-
_ = await distributed.hello() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
145+
distributed.hello() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
146146
_ = await distributed.helloAsync() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
147147
_ = try await distributed.helloAsyncThrows() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
148148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed -disable-availability-checking
2+
// REQUIRES: concurrency
3+
// REQUIRES: distributed
4+
5+
import _Distributed
6+
7+
struct SomeLogger {}
8+
struct Logger {
9+
let label: String
10+
func info(_: String) {}
11+
}
12+
13+
distributed actor Philosopher {
14+
let log: Logger
15+
// expected-note@-1{{distributed actor state is only available within the actor instance}}
16+
var variable = 12
17+
var variable_fromDetach = 12 // expected-note{{distributed actor state is only available within the actor instance}}
18+
let INITIALIZED: Int
19+
let outside: Int = 1
20+
21+
init(transport: ActorTransport) {
22+
self.log = Logger(label: "name")
23+
self.INITIALIZED = 1
24+
}
25+
26+
distributed func dist() -> Int {}
27+
28+
func test() {
29+
_ = self.id
30+
_ = self.actorTransport
31+
Task {
32+
_ = self.id
33+
_ = self.actorTransport
34+
35+
self.log.info("READY!")
36+
_ = self.variable
37+
_ = self.dist()
38+
}
39+
40+
Task.detached {
41+
_ = self.id
42+
_ = self.actorTransport
43+
44+
// This is an interesting case, since we have a real local `self` and
45+
// yet are not isolated to the same actor in this detached task...
46+
// the call to it is implicitly async, however it is NOT implicitly throwing
47+
// because we KNOW this is a local call -- and there is no transport in
48+
// between that will throw.
49+
_ = await self.dist() // notice lack of 'try' even though 'distributed func'
50+
_ = self.variable_fromDetach // expected-error{{distributed actor-isolated property 'variable_fromDetach' can only be referenced inside the distributed actor}}
51+
}
52+
}
53+
}
54+
55+
func test_outside(transport: ActorTransport) async throws {
56+
_ = try await Philosopher(transport: transport).dist()
57+
_ = Philosopher(transport: transport).log // expected-error{{distributed actor-isolated property 'log' can only be referenced inside the distributed actor}}
58+
59+
_ = Philosopher(transport: transport).id
60+
_ = Philosopher(transport: transport).actorTransport
61+
}
62+
63+
func test_outside_isolated(phil: isolated Philosopher) async throws {
64+
phil.log.info("works on isolated")
65+
}

test/decl/protocol/special/DistributedActor.swift

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed -verify-ignore-unknown
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed -disable-availability-checking -verify-ignore-unknown
22
// REQUIRES: concurrency
33
// REQUIRES: distributed
44

55
import _Distributed
66

7-
@available(SwiftStdlib 5.5, *)
87
distributed actor D1 {
98
var x: Int = 17
109
}
1110

12-
@available(SwiftStdlib 5.5, *)
1311
distributed actor D2 {
1412
// expected-error@-1{{actor 'D2' has no initializers}}
1513
let actorTransport: String
@@ -18,25 +16,32 @@ distributed actor D2 {
1816
// expected-note@-3{{stored property 'actorTransport' without initial value prevents synthesized initializers}}
1917
}
2018

21-
@available(SwiftStdlib 5.5, *)
2219
distributed actor D3 {
2320
var id: Int { 0 }
2421
// expected-error@-1{{property 'id' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}}
2522
// expected-error@-2{{invalid redeclaration of synthesized implementation for protocol requirement 'id'}}
2623
}
2724

28-
@available(SwiftStdlib 5.5, *)
2925
distributed actor D4 {
3026
// expected-error@-1{{actor 'D4' has no initializers}}
3127
let actorTransport: String
3228
// expected-error@-1{{invalid redeclaration of synthesized implementation for protocol requirement 'actorTransport'}}
3329
// expected-error@-2{{property 'actorTransport' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}}
3430
// expected-note@-3{{stored property 'actorTransport' without initial value prevents synthesized initializers}}
3531
let id: AnyActorIdentity
36-
// expected-error@-1{{actor-isolated property 'id' cannot be used to satisfy a protocol requirement}}
37-
// expected-error@-2{{property 'id' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}}
38-
// expected-error@-3{{actor-isolated property 'id' cannot be used to satisfy a protocol requirement}}
39-
// expected-note@-4{{stored property 'id' without initial value prevents synthesized initializers}}
32+
// expected-error@-1{{property 'id' cannot be defined explicitly, as it conflicts with distributed actor synthesized stored property}}
33+
// expected-note@-2{{stored property 'id' without initial value prevents synthesized initializers}}
34+
}
35+
36+
protocol P1: DistributedActor {
37+
distributed func dist() -> String
38+
// expected-note@-1{{distributed function requirement 'dist()' declared here}}
39+
}
40+
41+
distributed actor D5: P1 {
42+
func dist() -> String { "" }
43+
// expected-error@-1{{actor-isolated instance method 'dist()' cannot be used to satisfy a protocol requirement}}
44+
// expected-note@-2{{add 'distributed' to 'dist()' to make this instance method witness the protocol requirement}}{{3-3=distributed }}
4045
}
4146

4247
// ==== Tests ------------------------------------------------------------------

0 commit comments

Comments
 (0)