Skip to content

Commit 5f5d9e4

Browse files
authored
Merge pull request #34285 from DougGregor/global-actor-conformance-checking
[Concurrency] Implement global actor isolation checking for conformances
2 parents 0b456ba + 2d8f073 commit 5f5d9e4

File tree

4 files changed

+185
-32
lines changed

4 files changed

+185
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4194,6 +4194,18 @@ ERROR(actor_isolated_witness_could_be_async_handler,none,
41944194
"actor-isolated %0 %1 cannot be used to satisfy a protocol requirement; "
41954195
"did you mean to make it an asychronous handler?",
41964196
(DescriptiveDeclKind, DeclName))
4197+
ERROR(global_actor_isolated_requirement,none,
4198+
"%0 %1 must be isolated to the global actor %2 to satisfy corresponding "
4199+
"requirement from protocol %3",
4200+
(DescriptiveDeclKind, DeclName, Type, Identifier))
4201+
ERROR(global_actor_isolated_witness,none,
4202+
"%0 %1 isolated to global actor %2 can not satisfy corresponding "
4203+
"requirement from protocol %3",
4204+
(DescriptiveDeclKind, DeclName, Type, Identifier))
4205+
ERROR(global_actor_isolated_requirement_witness_conflict,none,
4206+
"%0 %1 isolated to global actor %2 can not satisfy corresponding "
4207+
"requirement from protocol %3 isolated to global actor %4",
4208+
(DescriptiveDeclKind, DeclName, Type, Identifier, Type))
41974209

41984210
ERROR(actorisolated_let,none,
41994211
"'@actorIsolated' is meaningless on 'let' declarations because "

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,6 +2655,114 @@ static void emitDeclaredHereIfNeeded(DiagnosticEngine &diags,
26552655
diags.diagnose(value, diag::decl_declared_here, value->getName());
26562656
}
26572657

2658+
bool ConformanceChecker::checkActorIsolation(
2659+
ValueDecl *requirement, ValueDecl *witness) {
2660+
// Ensure that the witness is not actor-isolated in a manner that makes it
2661+
// unsuitable as a witness.
2662+
Type witnessGlobalActor;
2663+
switch (auto witnessRestriction =
2664+
ActorIsolationRestriction::forDeclaration(witness)) {
2665+
case ActorIsolationRestriction::ActorSelf: {
2666+
// Actor-isolated witnesses cannot conform to protocol requirements.
2667+
bool canBeAsyncHandler = false;
2668+
if (auto witnessFunc = dyn_cast<FuncDecl>(witness)) {
2669+
canBeAsyncHandler = !witnessFunc->isAsyncHandler() &&
2670+
witnessFunc->canBeAsyncHandler();
2671+
}
2672+
auto diag = witness->diagnose(
2673+
canBeAsyncHandler
2674+
? diag::actor_isolated_witness_could_be_async_handler
2675+
: diag::actor_isolated_witness,
2676+
witness->getDescriptiveKind(), witness->getName());
2677+
2678+
if (canBeAsyncHandler) {
2679+
diag.fixItInsert(
2680+
witness->getAttributeInsertionLoc(false), "@asyncHandler ");
2681+
}
2682+
2683+
return true;
2684+
}
2685+
2686+
case ActorIsolationRestriction::GlobalActor: {
2687+
// Hang on to the global actor that's used for the witness. It will need
2688+
// to match that of the requirement.
2689+
witnessGlobalActor = witness->getDeclContext()->mapTypeIntoContext(
2690+
witnessRestriction.getGlobalActor());
2691+
break;
2692+
}
2693+
2694+
case ActorIsolationRestriction::Unsafe:
2695+
case ActorIsolationRestriction::LocalCapture:
2696+
break;
2697+
2698+
case ActorIsolationRestriction::Unrestricted:
2699+
// The witness is completely unrestricted, so ignore any annotations on
2700+
// the requirement.
2701+
return false;
2702+
}
2703+
2704+
// Check whether the requirement requires some particular actor isolation.
2705+
Type requirementGlobalActor;
2706+
switch (auto requirementIsolation = getActorIsolation(requirement)) {
2707+
case ActorIsolation::ActorInstance:
2708+
llvm_unreachable("There are not actor protocols");
2709+
2710+
case ActorIsolation::GlobalActor: {
2711+
auto requirementSubs = SubstitutionMap::getProtocolSubstitutions(
2712+
Proto, Adoptee, ProtocolConformanceRef(Conformance));
2713+
requirementGlobalActor = requirementIsolation.getGlobalActor()
2714+
.subst(requirementSubs);
2715+
break;
2716+
}
2717+
2718+
case ActorIsolation::Independent:
2719+
case ActorIsolation::Unspecified:
2720+
break;
2721+
}
2722+
2723+
// If neither has a global actor, we're done.
2724+
if (!witnessGlobalActor && !requirementGlobalActor)
2725+
return false;
2726+
2727+
// If the witness has a global actor but the requirement does not, we have
2728+
// an isolation error.
2729+
if (witnessGlobalActor && !requirementGlobalActor) {
2730+
witness->diagnose(
2731+
diag::global_actor_isolated_witness, witness->getDescriptiveKind(),
2732+
witness->getName(), witnessGlobalActor, Proto->getName());
2733+
requirement->diagnose(diag::decl_declared_here, requirement->getName());
2734+
return true;
2735+
}
2736+
2737+
// If the requirement has a global actor but the witness does not, we have
2738+
// an isolation error.
2739+
//
2740+
// FIXME: Within a module, this will be an inference rule.
2741+
if (requirementGlobalActor && !witnessGlobalActor) {
2742+
witness->diagnose(
2743+
diag::global_actor_isolated_requirement, witness->getDescriptiveKind(),
2744+
witness->getName(), requirementGlobalActor, Proto->getName())
2745+
.fixItInsert(
2746+
witness->getAttributeInsertionLoc(/*forModifier=*/false),
2747+
"@" + requirementGlobalActor.getString());
2748+
requirement->diagnose(diag::decl_declared_here, requirement->getName());
2749+
return true;
2750+
}
2751+
2752+
// If both have global actors but they differ, this is an isolation error.
2753+
if (!witnessGlobalActor->isEqual(requirementGlobalActor)) {
2754+
witness->diagnose(
2755+
diag::global_actor_isolated_requirement_witness_conflict,
2756+
witness->getDescriptiveKind(), witness->getName(), witnessGlobalActor,
2757+
Proto->getName(), requirementGlobalActor);
2758+
requirement->diagnose(diag::decl_declared_here, requirement->getName());
2759+
return true;
2760+
}
2761+
2762+
// Everything is okay.
2763+
return false;
2764+
}
2765+
26582766
bool ConformanceChecker::checkObjCTypeErasedGenerics(
26592767
AssociatedTypeDecl *assocType,
26602768
Type type,
@@ -4336,39 +4444,8 @@ void ConformanceChecker::resolveValueWitnesses() {
43364444
return;
43374445
}
43384446

4339-
// Check for actor-isolation consistency.
4340-
switch (auto restriction =
4341-
ActorIsolationRestriction::forDeclaration(witness)) {
4342-
case ActorIsolationRestriction::ActorSelf: {
4343-
// Actor-isolated witnesses cannot conform to protocol requirements.
4344-
bool canBeAsyncHandler = false;
4345-
if (auto witnessFunc = dyn_cast<FuncDecl>(witness)) {
4346-
canBeAsyncHandler = !witnessFunc->isAsyncHandler() &&
4347-
witnessFunc->canBeAsyncHandler();
4348-
}
4349-
auto diag = witness->diagnose(
4350-
canBeAsyncHandler
4351-
? diag::actor_isolated_witness_could_be_async_handler
4352-
: diag::actor_isolated_witness,
4353-
witness->getDescriptiveKind(), witness->getName());
4354-
4355-
if (canBeAsyncHandler) {
4356-
diag.fixItInsert(
4357-
witness->getAttributeInsertionLoc(false), "@asyncHandler ");
4358-
}
4447+
if (checkActorIsolation(requirement, witness))
43594448
return;
4360-
}
4361-
4362-
case ActorIsolationRestriction::GlobalActor: {
4363-
// FIXME: Check against the requirement. This needs serious refactoring.
4364-
break;
4365-
}
4366-
4367-
case ActorIsolationRestriction::Unrestricted:
4368-
case ActorIsolationRestriction::Unsafe:
4369-
case ActorIsolationRestriction::LocalCapture:
4370-
break;
4371-
}
43724449

43734450
// Objective-C checking for @objc requirements.
43744451
if (requirement->isObjC() &&

lib/Sema/TypeCheckProtocol.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,11 @@ class ConformanceChecker : public WitnessChecker {
740740
Type type,
741741
TypeDecl *typeDecl);
742742

743+
/// Check that the witness and requirement have compatible actor contexts.
744+
///
745+
/// \returns true if an error occurred, false otherwise.
746+
bool checkActorIsolation(ValueDecl *requirement, ValueDecl *witness);
747+
743748
/// Record a type witness.
744749
///
745750
/// \param assocType The associated type whose witness is being recorded.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
2+
// REQUIRES: concurrency
3+
4+
import _Concurrency
5+
6+
actor class SomeActor { }
7+
8+
@globalActor
9+
struct GlobalActor {
10+
static var shared: SomeActor { SomeActor() }
11+
}
12+
13+
@globalActor
14+
struct GenericGlobalActor<T> {
15+
static var shared: SomeActor { SomeActor() }
16+
}
17+
18+
protocol P1 {
19+
associatedtype Assoc
20+
21+
@GlobalActor func method1() // expected-note{{declared here}}
22+
@GenericGlobalActor<Int> func method2() // expected-note{{declared here}}
23+
@GenericGlobalActor<Assoc> func method3()
24+
func method4() // expected-note{{declared here}}
25+
}
26+
27+
protocol P2 {
28+
@GlobalActor func asyncMethod1() async
29+
@GenericGlobalActor<Int> func asyncMethod2() async
30+
func asyncMethod3() async
31+
}
32+
33+
class C1 : P1, P2 {
34+
typealias Assoc = String
35+
36+
// FIXME: This will be inferred
37+
func method1() { } // expected-error{{instance method 'method1()' must be isolated to the global actor 'GlobalActor' to satisfy corresponding requirement from protocol 'P1'}}{{3-3=@GlobalActor}}
38+
39+
@GenericGlobalActor<String> func method2() { } // expected-error{{instance method 'method2()' isolated to global actor 'GenericGlobalActor<String>' can not satisfy corresponding requirement from protocol 'P1' isolated to global actor 'GenericGlobalActor<Int>'}}
40+
@GenericGlobalActor<String >func method3() { }
41+
@GlobalActor func method4() { } // expected-error{{instance method 'method4()' isolated to global actor 'GlobalActor' can not satisfy corresponding requirement from protocol 'P1'}}
42+
43+
// Okay: we can ignore the mismatch in global actor types for 'async' methods.
44+
func asyncMethod1() async { }
45+
@GenericGlobalActor<String> func asyncMethod2() async { }
46+
@GlobalActor func asyncMethod3() async { }
47+
}
48+
49+
50+
class C2: P1 {
51+
typealias Assoc = Int
52+
53+
// Okay: we can ignore the mismatch in global actor types for 'asyncHandler'
54+
// methods.
55+
@asyncHandler func method1() { }
56+
@asyncHandler func method2() { }
57+
@asyncHandler func method3() { }
58+
@asyncHandler func method4() { }
59+
}

0 commit comments

Comments
 (0)