Skip to content

Commit 382cb85

Browse files
authored
Merge pull request #34678 from kavon/actor-method-promotion
2 parents 3321047 + 3e613a2 commit 382cb85

File tree

8 files changed

+397
-50
lines changed

8 files changed

+397
-50
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4213,9 +4213,10 @@ ERROR(actor_isolated_concurrent_access,none,
42134213
"actor-isolated %0 %1 is unsafe to reference in code "
42144214
"that may execute concurrently",
42154215
(DescriptiveDeclKind, DeclName))
4216-
NOTE(actor_isolated_method,none,
4217-
"only asynchronous methods can be used outside the actor instance; "
4218-
"do you want to add 'async'?", ())
4216+
NOTE(actor_isolated_sync_func,none,
4217+
"calls to %0 %1 from outside of its actor context are "
4218+
"implicitly asynchronous",
4219+
(DescriptiveDeclKind, DeclName))
42194220
NOTE(actor_mutable_state,none,
42204221
"mutable state is only available within the actor instance", ())
42214222
WARNING(shared_mutable_state_access,none,

include/swift/AST/Expr.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ class alignas(8) Expr {
334334
NumCaptures : 32
335335
);
336336

337-
SWIFT_INLINE_BITFIELD(ApplyExpr, Expr, 1+1,
337+
SWIFT_INLINE_BITFIELD(ApplyExpr, Expr, 1+1+1,
338338
ThrowsIsSet : 1,
339-
Throws : 1
339+
Throws : 1,
340+
ImplicitlyAsync : 1
340341
);
341342

342343
SWIFT_INLINE_BITFIELD_FULL(CallExpr, ApplyExpr, 1+1+16,
@@ -4308,6 +4309,7 @@ class ApplyExpr : public Expr {
43084309
assert(classof((Expr*)this) && "ApplyExpr::classof out of date");
43094310
assert(validateArg(Arg) && "Arg is not a permitted expr kind");
43104311
Bits.ApplyExpr.ThrowsIsSet = false;
4312+
Bits.ApplyExpr.ImplicitlyAsync = false;
43114313
}
43124314

43134315
public:
@@ -4346,6 +4348,29 @@ class ApplyExpr : public Expr {
43464348
Bits.ApplyExpr.Throws = throws;
43474349
}
43484350

4351+
/// Is this application _implicitly_ required to be an async call?
4352+
/// Note that this is _not_ a check for whether the callee is async!
4353+
/// Only meaningful after complete type-checking.
4354+
///
4355+
/// Generally, this comes up only when we have a non-self call to an actor
4356+
/// instance's synchronous method. Such calls are conceptually treated as if
4357+
/// they are wrapped with an async closure. For example,
4358+
///
4359+
/// act.syncMethod(a, b)
4360+
///
4361+
/// is equivalent to the eta-expanded version of act.syncMethod,
4362+
///
4363+
/// { (a1, b1) async in act.syncMethod(a1, b1) }(a, b)
4364+
///
4365+
/// where the new closure is declared to be async.
4366+
///
4367+
bool implicitlyAsync() const {
4368+
return Bits.ApplyExpr.ImplicitlyAsync;
4369+
}
4370+
void setImplicitlyAsync(bool flag) {
4371+
Bits.ApplyExpr.ImplicitlyAsync = flag;
4372+
}
4373+
43494374
ValueDecl *getCalledValue() const;
43504375

43514376
/// Retrieve the argument labels provided at the call site.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ namespace {
584584
class ActorIsolationChecker : public ASTWalker {
585585
ASTContext &ctx;
586586
SmallVector<const DeclContext *, 4> contextStack;
587+
SmallVector<ApplyExpr*, 4> applyStack;
587588

588589
const DeclContext *getDeclContext() const {
589590
return contextStack.back();
@@ -625,27 +626,45 @@ namespace {
625626
}
626627

627628
if (auto apply = dyn_cast<ApplyExpr>(expr)) {
629+
applyStack.push_back(apply); // record this encounter
630+
628631
// If this is a call to a partial apply thunk, decompose it to check it
629632
// like based on the original written syntax, e.g., "self.method".
630633
if (auto partialApply = decomposePartialApplyThunk(
631634
apply, Parent.getAsExpr())) {
632635
if (auto memberRef = findMemberReference(partialApply->fn)) {
636+
// NOTE: partially-applied thunks are never annotated as
637+
// implicitly async, regardless of whether they are escaping.
638+
// So, we do not pass the ApplyExpr along to checkMemberReference.
633639
checkMemberReference(
634640
partialApply->base, memberRef->first, memberRef->second,
635641
partialApply->isEscaping);
636642

637643
partialApply->base->walk(*this);
644+
645+
// manual clean-up since normal traversal is skipped
646+
assert(applyStack.back() == apply);
647+
applyStack.pop_back();
648+
638649
return { false, expr };
639650
}
640651
}
641652
}
642653

654+
// NOTE: SelfApplyExpr is a subtype of ApplyExpr
643655
if (auto call = dyn_cast<SelfApplyExpr>(expr)) {
644656
Expr *fn = call->getFn()->getValueProvidingExpr();
645657
if (auto memberRef = findMemberReference(fn)) {
646658
checkMemberReference(
647-
call->getArg(), memberRef->first, memberRef->second);
659+
call->getArg(), memberRef->first, memberRef->second,
660+
/*isEscapingPartialApply=*/false, call);
661+
648662
call->getArg()->walk(*this);
663+
664+
// manual clean-up since normal traversal is skipped
665+
assert(applyStack.back() == dyn_cast<ApplyExpr>(expr));
666+
applyStack.pop_back();
667+
649668
return { false, expr };
650669
}
651670
}
@@ -659,6 +678,11 @@ namespace {
659678
contextStack.pop_back();
660679
}
661680

681+
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
682+
assert(applyStack.back() == apply);
683+
applyStack.pop_back();
684+
}
685+
662686
return expr;
663687
}
664688

@@ -696,10 +720,9 @@ namespace {
696720
// FIXME: Make this diagnostic more sensitive to the isolation context
697721
// of the declaration.
698722
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
699-
// FIXME: We'd like to insert 'async' at the appropriate place, but
700-
// FuncDecl/AbstractFunctionDecl doesn't have the right source-location
701-
// information to do so.
702-
func->diagnose(diag::actor_isolated_method);
723+
func->diagnose(diag::actor_isolated_sync_func,
724+
decl->getDescriptiveKind(),
725+
decl->getName());
703726
} else if (isa<VarDecl>(decl)) {
704727
decl->diagnose(diag::actor_mutable_state);
705728
} else {
@@ -835,9 +858,39 @@ namespace {
835858
/// Check a reference to an entity within a global actor.
836859
bool checkGlobalActorReference(
837860
ValueDecl *value, SourceLoc loc, Type globalActor) {
861+
862+
/// Returns true if this global actor reference is the callee of an Apply.
863+
/// NOTE: This check mutates the identified ApplyExpr if it returns true!
864+
auto inspectForImplicitlyAsync = [&] () -> bool {
865+
866+
// Is this global actor reference outside of an ApplyExpr?
867+
if (applyStack.size() == 0)
868+
return false;
869+
870+
// Check our applyStack metadata from the traversal.
871+
// Our goal is to identify whether this global actor reference appears
872+
// as the called value of the enclosing ApplyExpr. We cannot simply
873+
// inspect Parent here because of expressions like (callee)()
874+
ApplyExpr *apply = applyStack.back();
875+
Expr *fn = apply->getFn()->getValueProvidingExpr();
876+
if (auto memberRef = findMemberReference(fn)) {
877+
auto concDecl = memberRef->first;
878+
if (value == concDecl.getDecl() && !apply->implicitlyAsync()) {
879+
// then this ValueDecl appears as the called value of the ApplyExpr.
880+
apply->setImplicitlyAsync(true);
881+
return true;
882+
}
883+
}
884+
885+
return false;
886+
};
887+
838888
switch (auto contextIsolation =
839889
getInnermostIsolatedContext(getDeclContext())) {
840890
case ActorIsolation::ActorInstance:
891+
if (inspectForImplicitlyAsync())
892+
return false;
893+
841894
ctx.Diags.diagnose(
842895
loc, diag::global_actor_from_instance_actor_context,
843896
value->getDescriptiveKind(), value->getName(), globalActor,
@@ -850,6 +903,12 @@ namespace {
850903
if (contextIsolation.getGlobalActor()->isEqual(globalActor))
851904
return false;
852905

906+
// Otherwise, we check if this decl reference is the callee of the
907+
// enclosing Apply, making it OK as an implicitly async call.
908+
if (inspectForImplicitlyAsync())
909+
return false;
910+
911+
// Otherwise, this is a problematic global actor decl reference.
853912
ctx.Diags.diagnose(
854913
loc, diag::global_actor_from_other_global_actor_context,
855914
value->getDescriptiveKind(), value->getName(), globalActor,
@@ -860,14 +919,18 @@ namespace {
860919

861920
case ActorIsolation::Independent:
862921
case ActorIsolation::IndependentUnsafe:
922+
if (inspectForImplicitlyAsync())
923+
return false;
924+
863925
ctx.Diags.diagnose(
864926
loc, diag::global_actor_from_independent_context,
865927
value->getDescriptiveKind(), value->getName(), globalActor);
866928
noteIsolatedActorMember(value);
867929
return true;
868930

869931
case ActorIsolation::Unspecified:
870-
// Okay.
932+
// Okay no matter what, but still must inspect for implicitly async.
933+
inspectForImplicitlyAsync();
871934
return false;
872935
}
873936
llvm_unreachable("unhandled actor isolation kind!");
@@ -918,9 +981,12 @@ namespace {
918981
}
919982

920983
/// Check a reference with the given base expression to the given member.
984+
/// Returns true iff the member reference refers to actor-isolated state
985+
/// in an invalid or unsafe way such that a diagnostic was emitted.
921986
bool checkMemberReference(
922987
Expr *base, ConcreteDeclRef memberRef, SourceLoc memberLoc,
923-
bool isEscapingPartialApply = false) {
988+
bool isEscapingPartialApply = false,
989+
ApplyExpr *maybeImplicitAsync = nullptr) {
924990
if (!base || !memberRef)
925991
return false;
926992

@@ -934,6 +1000,11 @@ namespace {
9341000
// Must reference actor-isolated state on 'self'.
9351001
auto selfVar = getSelfReference(base);
9361002
if (!selfVar) {
1003+
// actor-isolated non-self calls are implicitly async and thus OK.
1004+
if (maybeImplicitAsync && isa<AbstractFunctionDecl>(member)) {
1005+
maybeImplicitAsync->setImplicitlyAsync(true);
1006+
return false;
1007+
}
9371008
ctx.Diags.diagnose(
9381009
memberLoc, diag::actor_isolated_non_self_reference,
9391010
member->getDescriptiveKind(),

lib/Sema/TypeCheckEffects.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ class ApplyClassifier {
463463
auto fnType = type->getAs<AnyFunctionType>();
464464
if (!fnType) return Classification::forInvalidCode();
465465

466-
bool isAsync = fnType->isAsync();
466+
bool isAsync = fnType->isAsync() || E->implicitlyAsync();
467467

468468
// If the function doesn't throw at all, we're done here.
469469
if (!fnType->isThrowing())

0 commit comments

Comments
 (0)