@@ -2829,21 +2829,105 @@ bool ConformanceChecker::checkActorIsolation(
2829
2829
2830
2830
// An actor-isolated witness can only conform to an actor-isolated
2831
2831
// requirement.
2832
- if (requirementIsolation == ActorIsolation::ActorInstance)
2832
+ if (requirementIsolation == ActorIsolation::ActorInstance) {
2833
2833
return false ;
2834
+ }
2834
2835
2835
2836
auto witnessFunc = dyn_cast<AbstractFunctionDecl>(witness);
2836
2837
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
+ }
2837
2847
2838
2848
// / Distributed actors can witness protocol requirements either with
2839
2849
// / 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 ()) {
2846
2862
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 ;
2847
2931
}
2848
2932
}
2849
2933
@@ -2856,9 +2940,8 @@ bool ConformanceChecker::checkActorIsolation(
2856
2940
if (requirementFunc && requirementFunc->isDistributed ()) {
2857
2941
// a distributed protocol requirement can be witnessed with a
2858
2942
// 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 ())
2862
2945
.fixItInsert (witness->getAttributeInsertionLoc (true ),
2863
2946
" distributed " );
2864
2947
requirement
@@ -2878,10 +2961,72 @@ bool ConformanceChecker::checkActorIsolation(
2878
2961
return true ;
2879
2962
}
2880
2963
2881
- case ActorIsolationRestriction::CrossActorSelf:
2882
- return diagnoseNonSendableTypesInReference (
2964
+ case ActorIsolationRestriction::CrossActorSelf: {
2965
+ if ( diagnoseNonSendableTypesInReference (
2883
2966
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
+ }
2885
3030
2886
3031
case ActorIsolationRestriction::GlobalActorUnsafe:
2887
3032
witnessIsUnsafe = true ;
0 commit comments