Skip to content

Commit 591e6e8

Browse files
committed
[Concurrency] Only infer @asyncHandler for witnesses within actor classes.
Actor classes are by definition new code, so only perform inference of @asyncHandler from a protocol requirement to witnesses when those witnesses are within an actor class. This is partly because @asyncHandler might not have reasonable semantics outside of actor classes anywhere (where would you execute the detached task?), and partly because it is a source-compatibility problem due to the combination of... 1) Inferring @asyncHandler on imported Objective-C protocol methods for existing protocols, 2) Inferring @asyncHandler from those protocol methods to an existing method in a class that conforms to that protocol, and 3) Using an API that is now overloaded for both synchronous and asynchronous callers will end up preferring the asynchronous version, then fail due to a missing "await". The individual steps here are all reasonable, but the result is a source break, so back off on @asyncHandler inference for witnesses outside of actor classes. New code gets the more-asynchronous behavior for free.
1 parent 50f8705 commit 591e6e8

File tree

5 files changed

+52
-44
lines changed

5 files changed

+52
-44
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,16 @@ bool IsAsyncHandlerRequest::evaluate(
110110

111111
// Are we in a context where inference is possible?
112112
auto dc = func->getDeclContext();
113-
if (!dc->isTypeContext() || !dc->getParentSourceFile() ||
114-
isa<ProtocolDecl>(dc) || !func->hasBody())
113+
if (!dc->getSelfClassDecl() || !dc->getParentSourceFile() || !func->hasBody())
115114
return false;
116115

117116
// Is it possible to infer @asyncHandler for this function at all?
118117
if (!func->canBeAsyncHandler())
119118
return false;
120119

120+
if (!dc->getSelfClassDecl()->isActor())
121+
return false;
122+
121123
// Add an implicit @asyncHandler attribute and return true. We're done.
122124
auto addImplicitAsyncHandlerAttr = [&] {
123125
func->getAttrs().add(new (func->getASTContext()) AsyncHandlerAttr(true));

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -737,18 +737,6 @@ swift::matchWitness(
737737
}
738738
}
739739

740-
// Check for actor-isolation consistency.
741-
switch (getActorIsolation(witness)) {
742-
case ActorIsolation::ActorInstance:
743-
// Actor-isolated witnesses cannot conform to protocol requirements.
744-
return RequirementMatch(witness, MatchKind::ActorIsolatedWitness);
745-
746-
case ActorIsolation::ActorPrivileged:
747-
case ActorIsolation::Independent:
748-
case ActorIsolation::Unspecified:
749-
break;
750-
}
751-
752740
// Now finalize the match.
753741
auto result = finalize(anyRenaming, optionalAdjustments);
754742
// Check if the requirement's `@differentiable` attributes are satisfied by
@@ -2487,24 +2475,6 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
24872475
case MatchKind::NonObjC:
24882476
diags.diagnose(match.Witness, diag::protocol_witness_not_objc);
24892477
break;
2490-
case MatchKind::ActorIsolatedWitness: {
2491-
bool canBeAsyncHandler = false;
2492-
if (auto witnessFunc = dyn_cast<FuncDecl>(match.Witness)) {
2493-
canBeAsyncHandler = !witnessFunc->isAsyncHandler() &&
2494-
witnessFunc->canBeAsyncHandler();
2495-
}
2496-
auto diag = match.Witness->diagnose(
2497-
canBeAsyncHandler ? diag::actor_isolated_witness_could_be_async_handler
2498-
: diag::actor_isolated_witness,
2499-
match.Witness->getDescriptiveKind(), match.Witness->getName());
2500-
2501-
if (canBeAsyncHandler) {
2502-
diag.fixItInsert(
2503-
match.Witness->getAttributeInsertionLoc(false), "@asyncHandler ");
2504-
}
2505-
break;
2506-
}
2507-
25082478
case MatchKind::MissingDifferentiableAttr: {
25092479
auto *witness = match.Witness;
25102480
// Emit a note and fix-it showing the missing requirement `@differentiable`
@@ -4367,6 +4337,34 @@ void ConformanceChecker::resolveValueWitnesses() {
43674337
return;
43684338
}
43694339

4340+
// Check for actor-isolation consistency.
4341+
switch (getActorIsolation(witness)) {
4342+
case ActorIsolation::ActorInstance: {
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+
}
4359+
return;
4360+
}
4361+
4362+
case ActorIsolation::ActorPrivileged:
4363+
case ActorIsolation::Independent:
4364+
case ActorIsolation::Unspecified:
4365+
break;
4366+
}
4367+
43704368
// Objective-C checking for @objc requirements.
43714369
if (requirement->isObjC() &&
43724370
requirement->getName() == witness->getName() &&

lib/Sema/TypeCheckProtocol.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,6 @@ enum class MatchKind : uint8_t {
268268
/// The witness is explicitly @nonobjc but the requirement is @objc.
269269
NonObjC,
270270

271-
/// The witness is part of actor-isolated state.
272-
ActorIsolatedWitness,
273-
274271
/// The witness is missing a `@differentiable` attribute from the requirement.
275272
MissingDifferentiableAttr,
276273

@@ -503,7 +500,6 @@ struct RequirementMatch {
503500
case MatchKind::AsyncConflict:
504501
case MatchKind::ThrowsConflict:
505502
case MatchKind::NonObjC:
506-
case MatchKind::ActorIsolatedWitness:
507503
case MatchKind::MissingDifferentiableAttr:
508504
case MatchKind::EnumCaseWithAssociatedValues:
509505
return false;
@@ -536,7 +532,6 @@ struct RequirementMatch {
536532
case MatchKind::AsyncConflict:
537533
case MatchKind::ThrowsConflict:
538534
case MatchKind::NonObjC:
539-
case MatchKind::ActorIsolatedWitness:
540535
case MatchKind::MissingDifferentiableAttr:
541536
case MatchKind::EnumCaseWithAssociatedValues:
542537
return false;

test/attr/asynchandler.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,13 @@ func asyncHandlerBad3() throws { }
2727
func asyncHandlerBad4(result: inout Int) { }
2828
// expected-error@-1{{'inout' parameter is not allowed in '@asyncHandler' function}}
2929

30-
struct X {
30+
actor class X {
3131
@asyncHandler func asyncHandlerMethod() { }
3232

33-
@asyncHandler
34-
mutating func asyncHandlerMethodBad1() { }
35-
// expected-error@-1{{'@asyncHandler' function cannot be 'mutating'}}{{3-12=}}
36-
3733
@asyncHandler init() { }
3834
// expected-error@-1{{@asyncHandler may only be used on 'func' declarations}}
3935
}
4036

41-
4237
// Inference of @asyncHandler
4338
protocol P {
4439
@asyncHandler func callback()
@@ -50,3 +45,21 @@ extension X: P {
5045
let _ = await globalAsyncFunction()
5146
}
5247
}
48+
49+
class Y: P {
50+
// @asyncHandler is not inferred for classes
51+
52+
func callback() {
53+
// expected-note@-1{{add 'async' to function 'callback()' to make it asynchronous}}
54+
// expected-note@-2{{add '@asyncHandler' to function 'callback()' to create an implicit asynchronous context}}
55+
56+
// okay, it's an async context
57+
let _ = await globalAsyncFunction() // expected-error{{'async' in a function that does not support concurrency}}
58+
}
59+
}
60+
61+
struct Z {
62+
@asyncHandler
63+
mutating func asyncHandlerMethodBad1() { }
64+
// expected-error@-1{{'@asyncHandler' function cannot be 'mutating'}}{{3-12=}}
65+
}

test/decl/protocol/special/Actor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class C1: Actor {
4242

4343
// Method that is not usable as a witness.
4444
actor class BA1 {
45-
func enqueue(partialTask: PartialAsyncTask) { } // expected-error{{invalid redeclaration of synthesized implementation for protocol requirement 'enqueue(partialTask:)'}}
45+
func enqueue(partialTask: PartialAsyncTask) { } // expected-note{{actor-isolated instance method 'enqueue(partialTask:)' cannot be used to satisfy a protocol requirement; did you mean to make it an asychronous handler?}}
4646
}
4747

4848
// Method that isn't part of the main class definition cannot be used to

0 commit comments

Comments
 (0)