Skip to content

Commit 841bd42

Browse files
authored
[Concurrency][Distributed] Handle more cases of isolation, protocols, better errors (swiftlang#39895)
* [Distributed] Handle more cases of isolation, protocols, better errors/notes * [Concurrency] Actor sync funcs can conform to async protocol reqs * [Distributed] Offer fixit to add DistributedActor to type when dist func is encountered * [Distributed] Witness protocol reqs in dist actor protocol extensions * try to address availability issues
1 parent 8e154a4 commit 841bd42

15 files changed

+436
-109
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4351,6 +4351,10 @@ NOTE(note_add_async_to_function,none,
43514351
NOTE(note_add_nonisolated_to_decl,none,
43524352
"add 'nonisolated' to %0 to make this %1 not isolated to the actor",
43534353
(DeclName, DescriptiveDeclKind))
4354+
NOTE(note_add_async_and_throws_to_decl,none,
4355+
"mark the protocol requirement %0 '%select{|async|throws|async throws}1' "
4356+
"in order witness it with 'distributed' function declared in distributed actor %2",
4357+
(DeclName, unsigned, DeclName))
43544358
NOTE(note_add_distributed_to_decl,none,
43554359
"add 'distributed' to %0 to make this %1 witness the protocol requirement",
43564360
(DeclName, DescriptiveDeclKind))
@@ -4484,11 +4488,11 @@ NOTE(actor_isolated_sync_func,none,
44844488
"calls to %0 %1 from outside of its actor context are "
44854489
"implicitly asynchronous",
44864490
(DescriptiveDeclKind, DeclName))
4487-
NOTE(distributed_actor_isolated_method_note,none,
4488-
"only 'distributed' functions can be called from outside the distributed actor", // TODO(distributed): improve error message
4489-
())
4491+
NOTE(note_distributed_actor_isolated_method,none,
4492+
"distributed actor-isolated %0 %1 declared here",
4493+
(DescriptiveDeclKind, DeclName))
44904494
ERROR(distributed_actor_isolated_method,none,
4491-
"only 'distributed' functions can be called from outside the distributed actor", // TODO(distributed): improve error message to be more like 'non-distributed' ... defined here
4495+
"only 'distributed' functions can be called on a potentially remote distributed actor",
44924496
())
44934497
ERROR(distributed_actor_func_param_not_codable,none,
44944498
"distributed function parameter '%0' of type %1 does not conform to 'Codable'",
@@ -4497,7 +4501,7 @@ ERROR(distributed_actor_func_result_not_codable,none,
44974501
"distributed function result type %0 does not conform to 'Codable'",
44984502
(Type))
44994503
ERROR(distributed_actor_remote_func_implemented_manually,none,
4500-
"Distributed function's %0 remote counterpart %1 cannot not be implemented manually.",
4504+
"distributed function's %0 remote counterpart %1 cannot not be implemented manually.",
45014505
(Identifier, Identifier))
45024506
ERROR(nonisolated_distributed_actor_storage,none,
45034507
"'nonisolated' can not be applied to distributed actor stored properties",
@@ -4523,6 +4527,9 @@ WARNING(shared_mutable_state_access,none,
45234527
ERROR(actor_isolated_witness,none,
45244528
"actor-isolated %0 %1 cannot be used to satisfy a protocol requirement",
45254529
(DescriptiveDeclKind, DeclName))
4530+
ERROR(distributed_actor_isolated_witness,none,
4531+
"distributed actor-isolated %0 %1 cannot be used to satisfy a protocol requirement",
4532+
(DescriptiveDeclKind, DeclName))
45264533
ERROR(global_actor_isolated_requirement,none,
45274534
"%0 %1 must be isolated to the global actor %2 to satisfy corresponding "
45284535
"requirement from protocol %3",

lib/Sema/TypeCheckAttr.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "MiscDiagnostics.h"
1818
#include "TypeCheckAvailability.h"
1919
#include "TypeCheckConcurrency.h"
20+
#include "TypeCheckDistributed.h"
2021
#include "TypeCheckObjC.h"
2122
#include "TypeCheckType.h"
2223
#include "TypeChecker.h"
@@ -5453,9 +5454,10 @@ void AttributeChecker::visitDistributedActorAttr(DistributedActorAttr *attr) {
54535454
return;
54545455
} else if (auto protoDecl = dc->getSelfProtocolDecl()){
54555456
if (!protoDecl->inheritsFromDistributedActor()) {
5456-
// TODO: could suggest adding `: DistributedActor` to the protocol as well
5457-
diagnoseAndRemoveAttr(
5457+
auto diag = diagnoseAndRemoveAttr(
54585458
attr, diag::distributed_actor_func_not_in_distributed_actor);
5459+
diagnoseDistributedFunctionInNonDistributedActorProtocol(
5460+
protoDecl, diag);
54595461
return;
54605462
}
54615463
} else if (dc->getSelfStructDecl() || dc->getSelfEnumDecl()) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1604,7 +1604,9 @@ namespace {
16041604
decl->diagnose(diag::distributed_actor_isolated_property);
16051605
} else {
16061606
// it's a function or subscript
1607-
decl->diagnose(diag::distributed_actor_isolated_method_note);
1607+
decl->diagnose(diag::note_distributed_actor_isolated_method,
1608+
decl->getDescriptiveKind(),
1609+
decl->getName());
16081610
}
16091611
} else if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
16101612
func->diagnose(diag::actor_isolated_sync_func,

lib/Sema/TypeCheckDistributed.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ DistributedModuleIsAvailableRequest::evaluate(Evaluator &evaluator,
5353

5454
// ==== ------------------------------------------------------------------------
5555

56+
/// Add Fix-It text for the given protocol type to inherit DistributedActor.
57+
void swift::diagnoseDistributedFunctionInNonDistributedActorProtocol(
58+
const ProtocolDecl *proto, InFlightDiagnostic &diag) {
59+
if (proto->getInherited().empty()) {
60+
SourceLoc fixItLoc = proto->getBraces().Start;
61+
diag.fixItInsert(fixItLoc, ": DistributedActor");
62+
} else {
63+
// Similar to how Sendable FitIts do this, we insert at the end of
64+
// the inherited types.
65+
ASTContext &ctx = proto->getASTContext();
66+
SourceLoc fixItLoc = proto->getInherited().back().getSourceRange().End;
67+
fixItLoc = Lexer::getLocForEndOfToken(ctx.SourceMgr, fixItLoc);
68+
diag.fixItInsert(fixItLoc, ", DistributedActor");
69+
}
70+
}
71+
72+
// ==== ------------------------------------------------------------------------
73+
5674
bool IsDistributedActorRequest::evaluate(
5775
Evaluator &evaluator, NominalTypeDecl *nominal) const {
5876
// Protocols are actors if they inherit from `DistributedActor`.
@@ -63,7 +81,8 @@ bool IsDistributedActorRequest::evaluate(
6381
protocol->inheritsFrom(distributedActorProtocol));
6482
}
6583

66-
// Class declarations are 'distributed actors' if they are declared with 'distributed actor'
84+
// Class declarations are 'distributed actors' if they are declared with
85+
// 'distributed actor'
6786
auto classDecl = dyn_cast<ClassDecl>(nominal);
6887
if(!classDecl)
6988
return false;

lib/Sema/TypeCheckDistributed.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ bool checkDistributedFunction(FuncDecl *decl, bool diagnose);
4848
/// Determine the distributed actor transport type for the given actor.
4949
Type getDistributedActorTransportType(NominalTypeDecl *actor);
5050

51+
/// Diagnose a distributed func declaration in a not-distributed actor protocol.
52+
void diagnoseDistributedFunctionInNonDistributedActorProtocol(
53+
const ProtocolDecl *proto, InFlightDiagnostic &diag);
54+
5155
}
5256

5357

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 158 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2829,21 +2829,105 @@ bool ConformanceChecker::checkActorIsolation(
28292829

28302830
// An actor-isolated witness can only conform to an actor-isolated
28312831
// requirement.
2832-
if (requirementIsolation == ActorIsolation::ActorInstance)
2832+
if (requirementIsolation == ActorIsolation::ActorInstance) {
28332833
return false;
2834+
}
28342835

28352836
auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness);
28362837
auto requirementFunc = dyn_cast<AbstractFunctionDecl>(requirement);
2838+
auto nominal = dyn_cast<NominalTypeDecl>(witness->getDeclContext());
2839+
auto witnessClass = dyn_cast<ClassDecl>(witness->getDeclContext());
2840+
if (auto extension = dyn_cast<ExtensionDecl>(witness->getDeclContext())) {
2841+
// We can witness a distributed function in an extension, as long as
2842+
// that extension itself is on a DistributedActor type (including
2843+
// protocols that inherit from DistributedActor, even if the protocol
2844+
// requirement was not expressed in terms of distributed actors).
2845+
nominal = extension->getExtendedNominal();
2846+
}
28372847

28382848
/// Distributed actors can witness protocol requirements either with
28392849
/// nonisolated or distributed members.
2840-
auto witnessClass = dyn_cast<ClassDecl>(witness->getDeclContext());
2841-
if (witnessClass && witnessClass->isDistributedActor()) {
2842-
// Maybe we're dealing with a 'distributed func' which is witness to
2843-
// a distributed function requirement, this is ok.
2844-
if (requirementFunc && requirementFunc->isDistributed() &&
2845-
witnessFunc && witnessFunc->isDistributed()) {
2850+
if (nominal && nominal->isDistributedActor()) {
2851+
// A distributed actor may conform to an 'async throws' function
2852+
// requirement with a distributed function, because those are always
2853+
// cross-actor.
2854+
//
2855+
// If the distributed function is well-formed (passed checks) then it can
2856+
// witness this requirement. I.e. since checks to the distributed function
2857+
// passed, it can be called through this protocol.
2858+
if (witnessFunc && witnessFunc->isDistributed()) {
2859+
// If the requirement was also a 'distributed func' we most definitely
2860+
// can witness it with our 'distributed func' witness.
2861+
if (requirementFunc && requirementFunc->isDistributed()) {
28462862
return false;
2863+
}
2864+
if (requirementFunc->hasAsync() && requirementFunc->hasThrows()) {
2865+
return false;
2866+
}
2867+
2868+
if (requirementFunc) {
2869+
// The witness was distributed, but the requirement func was not
2870+
// 'async throws', so we can suggest adding those to the protocol
2871+
int suggestAddingModifiers = 0;
2872+
if (!requirementFunc->hasAsync()) suggestAddingModifiers += 1;
2873+
if (!requirementFunc->hasThrows()) suggestAddingModifiers += 2;
2874+
requirementFunc->diagnose(diag::note_add_async_and_throws_to_decl,
2875+
witness->getName(),
2876+
suggestAddingModifiers,
2877+
witnessClass ? witnessClass->getName() :
2878+
nominal->getName());
2879+
// TODO(distributed): fixit inserts for the async/throws
2880+
}
2881+
}
2882+
2883+
// The witness definitely is distributed actor isolated,
2884+
// so diagnose this as an error; next we'll provide helpful notes
2885+
witness->diagnose(diag::distributed_actor_isolated_witness,
2886+
witness->getDescriptiveKind(), witness->getName())
2887+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2888+
"distributed ");
2889+
2890+
// witness is not 'distributed', if the requirement was though,
2891+
// then we definitely must mark the witness distributed as well.
2892+
if (requirementFunc->isDistributed()) {
2893+
witness->diagnose(diag::note_add_distributed_to_decl,
2894+
witness->getName(),
2895+
witness->getDescriptiveKind())
2896+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2897+
"distributed ");
2898+
requirement->diagnose(diag::note_distributed_requirement_defined_here,
2899+
requirement->getName());
2900+
} else if (requirementFunc->hasAsync() && requirementFunc->hasThrows()) {
2901+
assert(!requirementFunc->isDistributed());
2902+
// If the requirement is 'async throws' we can add 'distributed' to the
2903+
// distributed actor function to witness the requirement.
2904+
witness->diagnose(diag::note_add_distributed_to_decl,
2905+
witness->getName(), witness->getDescriptiveKind())
2906+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2907+
"distributed ");
2908+
}
2909+
2910+
if (!requirementFunc->hasAsync() && !requirementFunc->isDistributed()) {
2911+
/// The requirement is synchronous, so we can only conform to it using
2912+
/// 'nonisolated'...
2913+
witness->diagnose(diag::note_add_nonisolated_to_decl,
2914+
witness->getName(), witness->getDescriptiveKind())
2915+
.fixItInsert(witness->getAttributeInsertionLoc(true),
2916+
"nonisolated ");
2917+
2918+
/// ... or by suggesting to make it 'async'
2919+
}
2920+
2921+
return true;
2922+
}
2923+
2924+
// A synchronous actor function can witness an asynchronous protocol
2925+
// requirement, since calls "through" the protocol are always cross-actor,
2926+
// in which case the function becomes implicitly async.
2927+
if (witnessClass && witnessClass->isActor()) {
2928+
if (requirementFunc && requirementFunc->hasAsync() &&
2929+
(requirementFunc->hasThrows() == witnessFunc->hasThrows())) {
2930+
return false;
28472931
}
28482932
}
28492933

@@ -2856,9 +2940,8 @@ bool ConformanceChecker::checkActorIsolation(
28562940
if (requirementFunc && requirementFunc->isDistributed()) {
28572941
// a distributed protocol requirement can be witnessed with a
28582942
// distributed function:
2859-
witness
2860-
->diagnose(diag::note_add_distributed_to_decl,
2861-
witness->getName(), witness->getDescriptiveKind())
2943+
witness->diagnose(diag::note_add_distributed_to_decl,
2944+
witness->getName(), witness->getDescriptiveKind())
28622945
.fixItInsert(witness->getAttributeInsertionLoc(true),
28632946
"distributed ");
28642947
requirement
@@ -2878,10 +2961,72 @@ bool ConformanceChecker::checkActorIsolation(
28782961
return true;
28792962
}
28802963

2881-
case ActorIsolationRestriction::CrossActorSelf:
2882-
return diagnoseNonSendableTypesInReference(
2964+
case ActorIsolationRestriction::CrossActorSelf: {
2965+
if (diagnoseNonSendableTypesInReference(
28832966
witness, DC->getParentModule(), witness->getLoc(),
2884-
ConcurrentReferenceKind::CrossActor);
2967+
ConcurrentReferenceKind::CrossActor)) {
2968+
return true;
2969+
}
2970+
2971+
auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness);
2972+
auto requirementFunc = dyn_cast<AbstractFunctionDecl>(requirement);
2973+
auto nominal = dyn_cast<NominalTypeDecl>(witness->getDeclContext());
2974+
auto witnessClass = dyn_cast<ClassDecl>(witness->getDeclContext());
2975+
if (auto extension = dyn_cast<ExtensionDecl>(witness->getDeclContext())) {
2976+
// We can witness a distributed function in an extension, as long as
2977+
// that extension itself is on a DistributedActor type (including
2978+
// protocols that inherit from DistributedActor, even if the protocol
2979+
// requirement was not expressed in terms of distributed actors).
2980+
nominal = extension->getExtendedNominal();
2981+
witnessClass = extension->getSelfClassDecl();
2982+
}
2983+
2984+
if (nominal && nominal->isDistributedActor()) {
2985+
// A distributed actor may conform to an 'async throws' function
2986+
// requirement with a distributed function, because those are always
2987+
// cross-actor.
2988+
//
2989+
// If the distributed function is well-formed (passed checks) then it can
2990+
// witness this requirement. I.e. since checks to the distributed function
2991+
// passed, it can be called through this protocol.
2992+
if (witnessFunc && witnessFunc->isDistributed()) {
2993+
// If the requirement was also a 'distributed func' we most definitely
2994+
// can witness it with our 'distributed func' witness.
2995+
if (requirementFunc && requirementFunc->isDistributed()) {
2996+
return false;
2997+
}
2998+
if (requirementFunc->hasAsync() && requirementFunc->hasThrows()) {
2999+
return false;
3000+
}
3001+
3002+
if (requirementFunc) {
3003+
// The witness was distributed, but the requirement func was not
3004+
// 'async throws', so we can suggest adding those to the protocol
3005+
int suggestAddingModifiers = 0;
3006+
if (!requirementFunc->hasAsync()) suggestAddingModifiers += 1;
3007+
if (!requirementFunc->hasThrows()) suggestAddingModifiers += 2;
3008+
requirementFunc->diagnose(diag::note_add_async_and_throws_to_decl,
3009+
witness->getName(),
3010+
suggestAddingModifiers,
3011+
witnessClass ? witnessClass->getName() :
3012+
nominal->getName());
3013+
// TODO(distributed): fixit inserts for the async/throws
3014+
} // TODO(distributed): handle computed properties as well?
3015+
}
3016+
3017+
if (witnessFunc) {
3018+
witness
3019+
->diagnose(diag::distributed_actor_isolated_witness,
3020+
witness->getDescriptiveKind(), witness->getName())
3021+
.fixItInsert(witness->getAttributeInsertionLoc(true),
3022+
"distributed ");
3023+
}
3024+
3025+
return true;
3026+
}
3027+
3028+
return false;
3029+
}
28853030

28863031
case ActorIsolationRestriction::GlobalActorUnsafe:
28873032
witnessIsUnsafe = true;

test/Concurrency/actor_isolation.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,17 @@ func testCrossActorProtocol<T: P>(t: T) async {
890890
ASPD.sd()
891891
}
892892

893+
@available(SwiftStdlib 5.5, *)
894+
protocol Server {
895+
func send<Message: Codable>(message: Message) async throws -> String
896+
}
897+
898+
@available(SwiftStdlib 5.5, *)
899+
actor MyServer : Server {
900+
// okay, asynchronously accessed from clients of the protocol
901+
func send<Message: Codable>(message: Message) throws -> String { "" }
902+
}
903+
893904
// ----------------------------------------------------------------------
894905
// @_inheritActorContext
895906
// ----------------------------------------------------------------------

0 commit comments

Comments
 (0)