@@ -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 ;
0 commit comments