Skip to content

Commit ae9e320

Browse files
authored
[Distributed] Handle distributed func witnessess from distributed actor protocols (#38269)
* [Distributed] Handle distributed func witnessess from distributed actor protocols * [Distributed] Implement dist protocol and nonisolated handling * revert additional warning check * [Distributed] Ban mixing nonisolated and distributed on func * [Distributed] handle nonisolated in distributed contexts
1 parent 5a13cc0 commit ae9e320

14 files changed

+301
-45
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,6 +3280,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
32803280
/// it is either a distributed actor.
32813281
bool isDistributedActor() const;
32823282

3283+
/// Whether this nominal type qualifies as any actor (plain or distributed).
3284+
bool isAnyActor() const;
3285+
32833286
/// Return the range of semantics attributes attached to this NominalTypeDecl.
32843287
auto getSemanticsAttrs() const
32853288
-> decltype(getAttrs().getSemanticsAttrs()) {

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4441,7 +4441,7 @@ NOTE(distributed_actor_isolated_method_note,none,
44414441
"only 'distributed' functions can be called from outside the distributed actor",
44424442
())
44434443
ERROR(distributed_actor_isolated_method,none,
4444-
"only 'distributed' functions can be called from outside the distributed actor", // TODO: more like 'non-distributed' ... defined here
4444+
"only 'distributed' functions can be called from outside the distributed actor", // TODO: improve error message to be more like 'non-distributed' ... defined here
44454445
())
44464446
ERROR(distributed_actor_func_param_not_codable,none,
44474447
"distributed function parameter '%0' of type %1 does not conform to 'Codable'",
@@ -4452,6 +4452,12 @@ ERROR(distributed_actor_func_result_not_codable,none,
44524452
ERROR(distributed_actor_remote_func_implemented_manually,none,
44534453
"Distributed function's %0 remote counterpart %1 cannot not be implemented manually.",
44544454
(Identifier, Identifier))
4455+
ERROR(nonisolated_distributed_actor_storage,none,
4456+
"'nonisolated' can not be applied to distributed actor stored properties",
4457+
())
4458+
ERROR(distributed_actor_func_nonisolated, none,
4459+
"function %0 cannot be both 'nonisolated' and 'distributed'",
4460+
(DeclName))
44554461
ERROR(distributed_actor_remote_func_is_not_static,none,
44564462
"remote function %0 must be static.",
44574463
(DeclName))
@@ -4600,7 +4606,7 @@ ERROR(distributed_actor_independent_property_must_be_let,none,
46004606
"_distributedActorIndependent can be applied to properties, however they must be 'let'",
46014607
())
46024608
NOTE(distributed_actor_isolated_property,none,
4603-
"distributed actor state is only available within the actor instance",
4609+
"distributed actor state is only available within the actor instance", // TODO: reword in terms of isolation
46044610
())
46054611

46064612
ERROR(concurrency_lib_missing,none,

lib/AST/ASTDumper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,10 @@ namespace {
855855
D->getCaptureInfo().print(OS);
856856
}
857857

858+
if (D->isDistributed()) {
859+
OS << " distributed";
860+
}
861+
858862
if (auto fac = D->getForeignAsyncConvention()) {
859863
OS << " foreign_async=";
860864
if (auto type = fac->completionHandlerType())

lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3895,6 +3895,10 @@ bool NominalTypeDecl::isDistributedActor() const {
38953895
false);
38963896
}
38973897

3898+
bool NominalTypeDecl::isAnyActor() const {
3899+
return isActor() || isDistributedActor();
3900+
}
3901+
38983902
GenericTypeDecl::GenericTypeDecl(DeclKind K, DeclContext *DC,
38993903
Identifier name, SourceLoc nameLoc,
39003904
ArrayRef<InheritedEntry> inherited,

lib/Sema/CodeSynthesisDistributedActor.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,6 @@ static void addImplicitResignAddress(ClassDecl *decl) {
527527
BraceStmt *newBody = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(),
528528
/*implicit=*/true);
529529

530-
newBody->dump();
531530
deinitDecl->setBody(newBody, AbstractFunctionDecl::BodyKind::TypeChecked); // FIXME: no idea if Parsed is right, we are NOT type checked I guess?
532531
}
533532

lib/Sema/TypeCheckAttr.cpp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5426,7 +5426,8 @@ void AttributeChecker::visitDistributedActorAttr(DistributedActorAttr *attr) {
54265426
return;
54275427
}
54285428
} else if (dyn_cast<StructDecl>(D) || dyn_cast<EnumDecl>(D)) {
5429-
diagnoseAndRemoveAttr(attr, diag::distributed_actor_func_not_in_distributed_actor);
5429+
diagnoseAndRemoveAttr(
5430+
attr, diag::distributed_actor_func_not_in_distributed_actor);
54305431
return;
54315432
}
54325433

@@ -5437,19 +5438,31 @@ void AttributeChecker::visitDistributedActorAttr(DistributedActorAttr *attr) {
54375438
return;
54385439
}
54395440

5441+
// distributed func cannot be simultaneously nonisolated
5442+
if (auto nonisolated =
5443+
funcDecl->getAttrs().getAttribute<NonisolatedAttr>()) {
5444+
diagnoseAndRemoveAttr(nonisolated,
5445+
diag::distributed_actor_func_nonisolated,
5446+
funcDecl->getName());
5447+
return;
5448+
}
5449+
54405450
// distributed func must be declared inside an distributed actor
54415451
if (dc->getSelfClassDecl() &&
54425452
!dc->getSelfClassDecl()->isDistributedActor()) {
5443-
diagnoseAndRemoveAttr(attr, diag::distributed_actor_func_not_in_distributed_actor);
5453+
diagnoseAndRemoveAttr(
5454+
attr, diag::distributed_actor_func_not_in_distributed_actor);
54445455
return;
54455456
} else if (auto protoDecl = dc->getSelfProtocolDecl()){
54465457
if (!protoDecl->inheritsFromDistributedActor()) {
54475458
// TODO: could suggest adding `: DistributedActor` to the protocol as well
5448-
diagnoseAndRemoveAttr(attr, diag::distributed_actor_func_not_in_distributed_actor);
5459+
diagnoseAndRemoveAttr(
5460+
attr, diag::distributed_actor_func_not_in_distributed_actor);
54495461
return;
54505462
}
54515463
} else if (dc->getSelfStructDecl() || dc->getSelfEnumDecl()) {
5452-
diagnoseAndRemoveAttr(attr, diag::distributed_actor_func_not_in_distributed_actor);
5464+
diagnoseAndRemoveAttr(
5465+
attr, diag::distributed_actor_func_not_in_distributed_actor);
54535466
return;
54545467
}
54555468
}
@@ -5459,11 +5472,27 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
54595472
// 'nonisolated' can be applied to global and static/class variables
54605473
// that do not have storage.
54615474
auto dc = D->getDeclContext();
5475+
54625476
if (auto var = dyn_cast<VarDecl>(D)) {
5463-
// 'nonisolated' can not be applied to mutable stored properties.
5464-
if (var->hasStorage() && var->supportsMutation()) {
5465-
diagnoseAndRemoveAttr(attr, diag::nonisolated_mutable_storage);
5466-
return;
5477+
// stored properties have limitations as to when they can be nonisolated.
5478+
if (var->hasStorage()) {
5479+
auto nominal = dyn_cast<NominalTypeDecl>(dc);
5480+
5481+
// 'nonisolated' can not be applied to stored properties inside
5482+
// distributed actors. Attempts of nonisolated access would be
5483+
// cross-actor, and that means they might be accessing on a remote actor,
5484+
// in which case the stored property storage does not exist.
5485+
if (nominal && nominal->isDistributedActor()) {
5486+
diagnoseAndRemoveAttr(attr,
5487+
diag::nonisolated_distributed_actor_storage);
5488+
return;
5489+
}
5490+
5491+
// 'nonisolated' can not be applied to mutable stored properties.
5492+
if (var->supportsMutation()) {
5493+
diagnoseAndRemoveAttr(attr, diag::nonisolated_mutable_storage);
5494+
return;
5495+
}
54675496
}
54685497

54695498
// nonisolated can not be applied to local properties.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,10 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
393393
diag::distributed_actor_func_defined_outside_of_distributed_actor,
394394
func->getName());
395395
}
396-
}
396+
} // TODO: need to handle protocol case here too?
397397

398-
return forDistributedActorSelf(isolation.getActor(),
399-
/*isCrossActor*/ isAccessibleAcrossActors); // TODO: not sure?
398+
return forDistributedActorSelf(isolation.getActor(),
399+
/*isCrossActor*/ isAccessibleAcrossActors);
400400
}
401401
}
402402

@@ -438,7 +438,6 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
438438
}
439439

440440
case ActorIsolation::Independent:
441-
// Actor-independent have no restrictions on their access.
442441
return forUnrestricted();
443442

444443
case ActorIsolation::Unspecified:
@@ -2264,8 +2263,27 @@ namespace {
22642263
switch (auto isolation =
22652264
ActorIsolationRestriction::forDeclaration(
22662265
memberRef, getDeclContext())) {
2267-
case ActorIsolationRestriction::Unrestricted:
2266+
case ActorIsolationRestriction::Unrestricted: {
2267+
// If a cross-actor reference is to an isolated actor, it's not
2268+
// crossing actors.
2269+
if (getIsolatedActor(base))
2270+
return false;
2271+
2272+
// Always fine to invoke constructors from outside of actors.
2273+
if (dyn_cast<ConstructorDecl>(member))
2274+
return false;
2275+
2276+
// While the member may be unrestricted, perhaps
2277+
if (auto classDecl = dyn_cast<ClassDecl>(member->getDeclContext())) {
2278+
if (classDecl->isDistributedActor()) {
2279+
ctx.Diags.diagnose(memberLoc, diag::distributed_actor_isolated_method);
2280+
noteIsolatedActorMember(member, context);
2281+
return true;
2282+
}
2283+
}
2284+
22682285
return false;
2286+
}
22692287

22702288
case ActorIsolationRestriction::CrossActorSelf: {
22712289
// If a cross-actor reference is to an isolated actor, it's not
@@ -2295,7 +2313,6 @@ namespace {
22952313
member->isInstanceMember() &&
22962314
isActorInitOrDeInitContext(getDeclContext()))) {
22972315
// invocation on not-'self', is only okey if this is a distributed func
2298-
22992316
if (auto func = dyn_cast<FuncDecl>(member)) {
23002317
if (!func->isDistributed()) {
23012318
ctx.Diags.diagnose(memberLoc, diag::distributed_actor_isolated_method);
@@ -2411,6 +2428,14 @@ namespace {
24112428
case ActorIsolationRestriction::Unsafe:
24122429
// This case is hit when passing actor state inout to functions in some
24132430
// cases. The error is emitted by diagnoseInOutArg.
2431+
2432+
if (auto classDecl = dyn_cast<ClassDecl>(member->getDeclContext())) {
2433+
if (classDecl->isDistributedActor()) {
2434+
member->diagnose(diag::distributed_actor_isolated_method);
2435+
return true;
2436+
}
2437+
}
2438+
24142439
return false;
24152440
}
24162441
llvm_unreachable("unhandled actor isolation kind!");
@@ -3132,11 +3157,17 @@ ActorIsolation ActorIsolationRequest::evaluate(
31323157
// overridden by other inference rules.
31333158
ActorIsolation defaultIsolation = ActorIsolation::forUnspecified();
31343159

3135-
// A @Sendable function is assumed to be actor-independent.
31363160
if (auto func = dyn_cast<AbstractFunctionDecl>(value)) {
3161+
// A @Sendable function is assumed to be actor-independent.
31373162
if (func->isSendable()) {
31383163
defaultIsolation = ActorIsolation::forIndependent();
31393164
}
3165+
3166+
if (auto nominal = value->getDeclContext()->getSelfNominalTypeDecl()) {
3167+
if (nominal->isDistributedActor()) {
3168+
defaultIsolation = ActorIsolation::forDistributedActorInstance(nominal);
3169+
}
3170+
}
31403171
}
31413172

31423173
// An actor's convenience init is assumed to be actor-independent.
@@ -3172,10 +3203,14 @@ ActorIsolation ActorIsolationRequest::evaluate(
31723203
}
31733204

31743205
case ActorIsolation::DistributedActorInstance: {
3175-
/// 'distributed actor independent' implies 'actor independent'
3176-
if (value->isDistributedActorIndependent())
3206+
/// 'distributed actor independent' implies 'nonisolated'
3207+
if (value->isDistributedActorIndependent()) {
3208+
// TODO: rename 'distributed actor independent' to 'distributed(nonisolated)'
31773209
value->getAttrs().add(
31783210
new (ctx) DistributedActorIndependentAttr(/*IsImplicit=*/true));
3211+
value->getAttrs().add(
3212+
new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
3213+
}
31793214
break;
31803215
}
31813216
case ActorIsolation::ActorInstance:
@@ -3186,6 +3221,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
31863221
// Nothing to do.
31873222
break;
31883223
}
3224+
31893225
return inferred;
31903226
};
31913227

@@ -3291,7 +3327,7 @@ bool HasIsolatedSelfRequest::evaluate(
32913327
// Only ever applies to members of actors.
32923328
auto dc = value->getDeclContext();
32933329
auto selfTypeDecl = dc->getSelfNominalTypeDecl();
3294-
if (!selfTypeDecl || !selfTypeDecl->isActor())
3330+
if (!selfTypeDecl || !selfTypeDecl->isAnyActor())
32953331
return false;
32963332

32973333
// For accessors, consider the storage declaration.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2827,31 +2827,28 @@ bool ConformanceChecker::checkActorIsolation(
28272827
/*fromExpression=*/false)) {
28282828
case ActorIsolationRestriction::DistributedActorSelf: {
28292829
if (witness->isSynthesized()) {
2830-
// Some of our synthesized properties get special treatment,
2831-
// they are always available, regardless if the actor is remote even.
2832-
auto &C = requirement->getASTContext();
2833-
2834-
// actorAddress is special, it is *always* available.
2835-
// even if the actor is 'remote' it is always available and immutable.
2836-
if (witness->getName() == C.Id_actorAddress &&
2837-
witness->getInterfaceType()->isEqual(
2838-
C.getActorAddressDecl()->getDeclaredInterfaceType()))
2830+
// we only have two 'distributed-actor-nonisolated' properties,
2831+
// the address and transport; if we see any such marked property,
2832+
// we're free to automatically assume those are fine and accessible always.
2833+
if (witness->isDistributedActorIndependent())
28392834
return false;
2835+
}
28402836

2841-
// TODO: we don't *really* need to expose the transport like that... reconsider?
2842-
if (witness->getName() == C.Id_actorTransport &&
2843-
witness->getInterfaceType()->isEqual(
2844-
C.getActorTransportDecl()->getDeclaredInterfaceType()))
2837+
if (auto funcDecl = dyn_cast<AbstractFunctionDecl>(witness)) {
2838+
// A 'distributed func' may witness a distributed isolated function requirement.
2839+
if (funcDecl->isDistributed())
28452840
return false;
28462841
}
28472842

28482843
// continue checking ActorSelf rules
28492844
LLVM_FALLTHROUGH;
28502845
}
28512846
case ActorIsolationRestriction::ActorSelf: {
2847+
auto requirementIsolation = getActorIsolation(requirement);
2848+
28522849
// An actor-isolated witness can only conform to an actor-isolated
28532850
// requirement.
2854-
if (getActorIsolation(requirement) == ActorIsolation::ActorInstance)
2851+
if (requirementIsolation == ActorIsolation::ActorInstance)
28552852
return false;
28562853

28572854
witness->diagnose(diag::actor_isolated_witness,
@@ -2903,8 +2900,12 @@ bool ConformanceChecker::checkActorIsolation(
29032900
bool requirementIsUnsafe = false;
29042901
switch (auto requirementIsolation = getActorIsolation(requirement)) {
29052902
case ActorIsolation::ActorInstance:
2906-
case ActorIsolation::DistributedActorInstance:
29072903
llvm_unreachable("There are not actor protocols");
2904+
case ActorIsolation::DistributedActorInstance:
2905+
// A requirement inside a distributed actor, where it has a protocol that was
2906+
// bound requiring a `DistributedActor` conformance (`protocol D: DistributedActor`),
2907+
// results in the requirement being isolated to given distributed actor.
2908+
break;
29082909

29092910
case ActorIsolation::GlobalActorUnsafe:
29102911
requirementIsUnsafe = true;

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ extension DistributedActor {
9797
fatalError("\(#function) is not implemented yet for distributed actors'")
9898
}
9999

100+
// FIXME: distributed(nonisolated)
100101
nonisolated public func encode(to encoder: Encoder) throws {
101102
var container = encoder.singleValueContainer()
102103
try container.encode(self.actorAddress)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed
2+
// REQUIRES: concurrency
3+
// REQUIRES: distributed
4+
5+
import _Distributed
6+
7+
@available(SwiftStdlib 5.5, *)
8+
distributed struct StructNope {} // expected-error{{distributed' modifier cannot be applied to this declaration}}
9+
@available(SwiftStdlib 5.5, *)
10+
distributed class ClassNope {} // expected-error{{'distributed' can only be applied to 'actor' definitions, and distributed actor-isolated async functions}}
11+
@available(SwiftStdlib 5.5, *)
12+
distributed enum EnumNope {} // expected-error{{distributed' modifier cannot be applied to this declaration}}
13+
14+
@available(SwiftStdlib 5.5, *)
15+
distributed actor DA {
16+
17+
class func classFunc() {
18+
// expected-error@-1{{class methods are only allowed within classes; use 'static' to declare a static method}}
19+
}
20+
21+
nonisolated distributed func nonisolatedDistributed() async {
22+
// expected-error@-1{{function 'nonisolatedDistributed()' cannot be both 'nonisolated' and 'distributed'}}{{3-15=}}
23+
fatalError()
24+
}
25+
26+
distributed nonisolated func distributedNonisolated() async {
27+
// expected-error@-1{{function 'distributedNonisolated()' cannot be both 'nonisolated' and 'distributed'}}{{15-27=}}
28+
fatalError()
29+
}
30+
}

test/Distributed/distributed_actor_initialization.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,3 @@ distributed actor BadRedeclare22 { //expected-error {{type 'BadRedeclare22' does
156156
// expected-error@-3 {{invalid redeclaration of synthesized 'init(resolve:using:)'}}
157157
// expected-error@-4 {{invalid redeclaration of synthesized initializer 'init(resolve:using:)'}}
158158
}
159-
160-
// TODO: handle subclassing as well

test/Distributed/distributed_actor_isolation.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ actor LocalActor_1 {
1616

1717
struct NotCodableValue { }
1818

19-
@available(SwiftStdlib 5.5, *)
20-
distributed struct StructNope {} // expected-error{{distributed' modifier cannot be applied to this declaration}}
21-
@available(SwiftStdlib 5.5, *)
22-
distributed class ClassNope {} // expected-error{{'distributed' can only be applied to 'actor' definitions, and distributed actor-isolated async functions}}
23-
@available(SwiftStdlib 5.5, *)
24-
distributed enum EnumNope {} // expected-error{{distributed' modifier cannot be applied to this declaration}}
25-
2619
@available(SwiftStdlib 5.5, *)
2720
distributed actor DistributedActor_1 {
2821

0 commit comments

Comments
 (0)