Skip to content

Commit 1dacc88

Browse files
committed
[Concurrency] Allow default arguments to require actor isolation.
Type checking a default argument expression will compute the required actor isolation for evaluating that argument value synchronously. Actor isolation checking is deferred to the caller; it is an error to use a default argument from across isolation domains. Currently gated behind -enable-experimental-feature IsolatedDefaultArguments. (cherry picked from commit 95a7107)
1 parent 86c87a4 commit 1dacc88

13 files changed

+276
-2
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6377,6 +6377,11 @@ class ParamDecl : public VarDecl {
63776377
/// at the call site in order to have the correct context information.
63786378
Expr *getTypeCheckedDefaultExpr() const;
63796379

6380+
/// The actor isolation required of the caller in order to use the
6381+
/// default argument for this parameter. If the required isolation is
6382+
/// not met, an argument must be written explicitly at the call-site.
6383+
ActorIsolation getDefaultArgumentIsolation() const;
6384+
63806385
/// Retrieve the potentially un-type-checked default argument expression for
63816386
/// this parameter, which can be queried for information such as its source
63826387
/// range and textual representation. Returns \c nullptr if there is no

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5192,6 +5192,10 @@ ERROR(distributed_actor_isolated_non_self_reference,none,
51925192
"distributed actor-isolated %kind0 can not be accessed from a "
51935193
"non-isolated context",
51945194
(const ValueDecl *))
5195+
ERROR(isolated_default_argument,none,
5196+
"%0 default argument cannot be synchronously evaluated from a "
5197+
"%1 context",
5198+
(ActorIsolation, ActorIsolation))
51955199
ERROR(distributed_actor_needs_explicit_distributed_import,none,
51965200
"'Distributed' module not imported, required for 'distributed actor'",
51975201
())

include/swift/AST/Expr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4538,6 +4538,11 @@ class DefaultArgumentExpr final : public Expr {
45384538
/// expression within the context of the call site.
45394539
Expr *getCallerSideDefaultExpr() const;
45404540

4541+
/// Get the required actor isolation for evaluating this default argument
4542+
/// synchronously. If the caller does not meet the required isolation, the
4543+
/// argument must be written explicitly at the call-site.
4544+
ActorIsolation getRequiredIsolation() const;
4545+
45414546
static bool classof(const Expr *E) {
45424547
return E->getKind() == ExprKind::DefaultArgument;
45434548
}

include/swift/AST/TypeCheckRequests.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,24 @@ class DefaultArgumentTypeRequest
29112911
void cacheResult(Type type) const;
29122912
};
29132913

2914+
/// Compute the actor isolation needed to synchronously evaluate the
2915+
/// default argument for the given parameter.
2916+
class DefaultArgumentIsolation
2917+
: public SimpleRequest<DefaultArgumentIsolation,
2918+
ActorIsolation(ParamDecl *),
2919+
RequestFlags::Cached> {
2920+
public:
2921+
using SimpleRequest::SimpleRequest;
2922+
2923+
private:
2924+
friend SimpleRequest;
2925+
2926+
ActorIsolation evaluate(Evaluator &evaluator, ParamDecl *param) const;
2927+
2928+
public:
2929+
bool isCached() const { return true; }
2930+
};
2931+
29142932
/// Computes the fully type-checked caller-side default argument within the
29152933
/// context of the call site that it will be inserted into.
29162934
class CallerSideDefaultArgExprRequest

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ SWIFT_REQUEST(TypeChecker, DefaultArgumentExprRequest,
6262
Expr *(ParamDecl *), SeparatelyCached, NoLocationInfo)
6363
SWIFT_REQUEST(TypeChecker, DefaultArgumentTypeRequest,
6464
Type(ParamDecl *), SeparatelyCached, NoLocationInfo)
65+
SWIFT_REQUEST(TypeChecker, DefaultArgumentIsolation,
66+
ActorIsolation(ParamDecl *), Cached, NoLocationInfo)
6567
SWIFT_REQUEST(TypeChecker, DefaultArgumentInitContextRequest,
6668
Initializer *(ParamDecl *), SeparatelyCached, NoLocationInfo)
6769
SWIFT_REQUEST(TypeChecker, DefaultDefinitionTypeRequest,

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ EXPERIMENTAL_FEATURE(SendNonSendable, false)
222222
/// Within strict concurrency, narrow global variable isolation requirements.
223223
EXPERIMENTAL_FEATURE(GlobalConcurrency, false)
224224

225+
/// Allow default arguments to require isolation at the call-site.
226+
EXPERIMENTAL_FEATURE(IsolatedDefaultArguments, false)
227+
225228
/// Enable extended callbacks (with additional parameters) to be used when the
226229
/// "playground transform" is enabled.
227230
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3502,6 +3502,10 @@ static bool usesFeatureSendNonSendable(Decl *decl) {
35023502

35033503
static bool usesFeatureGlobalConcurrency(Decl *decl) { return false; }
35043504

3505+
static bool usesFeatureIsolatedDefaultArguments(Decl *decl) {
3506+
return false;
3507+
}
3508+
35053509
static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
35063510
return false;
35073511
}

lib/AST/Decl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8158,6 +8158,14 @@ Type ParamDecl::getTypeOfDefaultExpr() const {
81588158
return Type();
81598159
}
81608160

8161+
ActorIsolation ParamDecl::getDefaultArgumentIsolation() const {
8162+
auto &ctx = getASTContext();
8163+
return evaluateOrDefault(
8164+
ctx.evaluator,
8165+
DefaultArgumentIsolation{const_cast<ParamDecl *>(this)},
8166+
ActorIsolation::forNonisolated());
8167+
}
8168+
81618169
void ParamDecl::setDefaultExpr(Expr *E, bool isTypeChecked) {
81628170
if (!DefaultValueAndFlags.getPointer()) {
81638171
if (!E) return;

lib/AST/Expr.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,12 @@ Expr *DefaultArgumentExpr::getCallerSideDefaultExpr() const {
16011601
new (ctx) ErrorExpr(getSourceRange(), getType()));
16021602
}
16031603

1604+
ActorIsolation
1605+
DefaultArgumentExpr::getRequiredIsolation() const {
1606+
auto *param = getParamDecl();
1607+
return param->getDefaultArgumentIsolation();
1608+
}
1609+
16041610
ValueDecl *ApplyExpr::getCalledValue(bool skipFunctionConversions) const {
16051611
return ::getCalledValue(Fn, skipFunctionConversions);
16061612
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,12 @@ namespace {
19491949
llvm::function_ref<ActorIsolation(AbstractClosureExpr *)>
19501950
getClosureActorIsolation;
19511951

1952+
bool shouldRefineIsolation = false;
1953+
1954+
/// Used under the mode to compute required actor isolation for
1955+
/// an expression or function.
1956+
ActorIsolation requiredIsolation = ActorIsolation::forNonisolated();
1957+
19521958
/// Keeps track of the capture context of variables that have been
19531959
/// explicitly captured in closures.
19541960
llvm::SmallDenseMap<VarDecl *, TinyPtrVector<const DeclContext *>>
@@ -2140,6 +2146,65 @@ namespace {
21402146
}
21412147
}
21422148

2149+
bool refineRequiredIsolation(ActorIsolation refinedIsolation) {
2150+
if (!shouldRefineIsolation)
2151+
return false;
2152+
2153+
if (auto *closure = dyn_cast<AbstractClosureExpr>(getDeclContext())) {
2154+
// We cannot infer a more specific actor isolation for a @Sendable
2155+
// closure. It is an error to cast away actor isolation from a function
2156+
// type, but this is okay for non-Sendable closures because they cannot
2157+
// leave the isolation domain they're created in anyway.
2158+
if (closure->isSendable())
2159+
return false;
2160+
2161+
if (closure->getActorIsolation().isActorIsolated())
2162+
return false;
2163+
}
2164+
2165+
// To refine the required isolation, the existing requirement
2166+
// must either be 'nonisolated' or exactly the same as the
2167+
// new refinement.
2168+
if (requiredIsolation == ActorIsolation::Nonisolated ||
2169+
requiredIsolation == refinedIsolation) {
2170+
requiredIsolation = refinedIsolation;
2171+
return true;
2172+
}
2173+
2174+
return false;
2175+
}
2176+
2177+
void checkDefaultArgument(DefaultArgumentExpr *expr) {
2178+
// Check the context isolation against the required isolation for
2179+
// evaluating the default argument synchronously. If the default
2180+
// argument must be evaluated asynchronously, it must be written
2181+
// explicitly in the argument list with 'await'.
2182+
auto requiredIsolation = expr->getRequiredIsolation();
2183+
auto contextIsolation = getInnermostIsolatedContext(
2184+
getDeclContext(), getClosureActorIsolation);
2185+
2186+
if (requiredIsolation == contextIsolation)
2187+
return;
2188+
2189+
switch (requiredIsolation) {
2190+
// Nonisolated is okay from any caller isolation because
2191+
// default arguments cannot have any async calls.
2192+
case ActorIsolation::Unspecified:
2193+
case ActorIsolation::Nonisolated:
2194+
return;
2195+
2196+
case ActorIsolation::GlobalActor:
2197+
case ActorIsolation::GlobalActorUnsafe:
2198+
case ActorIsolation::ActorInstance:
2199+
break;
2200+
}
2201+
2202+
auto &ctx = getDeclContext()->getASTContext();
2203+
ctx.Diags.diagnose(
2204+
expr->getLoc(), diag::isolated_default_argument,
2205+
requiredIsolation, contextIsolation);
2206+
}
2207+
21432208
/// Check closure captures for Sendable violations.
21442209
void checkLocalCaptures(AnyFunctionRef localFunc) {
21452210
SmallVector<CapturedValue, 2> captures;
@@ -2197,6 +2262,16 @@ namespace {
21972262
contextStack.push_back(dc);
21982263
}
21992264

2265+
ActorIsolation computeRequiredIsolation(Expr *expr) {
2266+
auto &ctx = getDeclContext()->getASTContext();
2267+
2268+
shouldRefineIsolation =
2269+
ctx.LangOpts.hasFeature(Feature::IsolatedDefaultArguments);
2270+
expr->walk(*this);
2271+
shouldRefineIsolation = false;
2272+
return requiredIsolation;
2273+
}
2274+
22002275
/// Searches the applyStack from back to front for the inner-most CallExpr
22012276
/// and marks that CallExpr as implicitly async.
22022277
///
@@ -2398,6 +2473,10 @@ namespace {
23982473
checkFunctionConversion(funcConv);
23992474
}
24002475

2476+
if (auto *defaultArg = dyn_cast<DefaultArgumentExpr>(expr)) {
2477+
checkDefaultArgument(defaultArg);
2478+
}
2479+
24012480
return Action::Continue(expr);
24022481
}
24032482

@@ -2957,6 +3036,9 @@ namespace {
29573036
if (!unsatisfiedIsolation)
29583037
return false;
29593038

3039+
if (refineRequiredIsolation(*unsatisfiedIsolation))
3040+
return false;
3041+
29603042
// At this point, we know a jump is made to the callee that yields
29613043
// an isolation requirement unsatisfied by the calling context, so
29623044
// set the unsatisfiedIsolationJump fields of the ApplyExpr appropriately
@@ -3294,6 +3376,14 @@ namespace {
32943376

32953377
case AsyncMarkingResult::SyncContext:
32963378
case AsyncMarkingResult::NotFound:
3379+
// If we found an implicitly async reference in a sync
3380+
// context and we're computing the required isolation for
3381+
// an expression, the calling context requires the isolation
3382+
// of the reference.
3383+
if (refineRequiredIsolation(result.isolation)) {
3384+
return false;
3385+
}
3386+
32973387
// Complain about access outside of the isolation domain.
32983388
auto useKind = static_cast<unsigned>(
32993389
kindOfUsage(decl, context).value_or(VarRefUseEnv::Read));
@@ -3506,6 +3596,12 @@ void swift::checkInitializerActorIsolation(Initializer *init, Expr *expr) {
35063596
expr->walk(checker);
35073597
}
35083598

3599+
ActorIsolation
3600+
swift::computeRequiredIsolation(Initializer *init, Expr *expr) {
3601+
ActorIsolationChecker checker(init);
3602+
return checker.computeRequiredIsolation(expr);
3603+
}
3604+
35093605
void swift::checkEnumElementActorIsolation(
35103606
EnumElementDecl *element, Expr *expr) {
35113607
ActorIsolationChecker checker(element);

lib/Sema/TypeCheckConcurrency.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ void checkInitializerActorIsolation(Initializer *init, Expr *expr);
6060
void checkEnumElementActorIsolation(EnumElementDecl *element, Expr *expr);
6161
void checkPropertyWrapperActorIsolation(VarDecl *wrappedVar, Expr *expr);
6262

63+
/// Compute the actor isolation required in order to evaluate the given
64+
/// initializer expression synchronously.
65+
ActorIsolation computeRequiredIsolation(Initializer *init, Expr *expr);
66+
6367
/// States the reason for checking the Sendability of a given declaration.
6468
enum class SendableCheckReason {
6569
/// A reference to an actor from outside that actor.

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,8 @@ static void checkDefaultArguments(ParameterList *params) {
10691069
for (auto *param : *params) {
10701070
auto ifacety = param->getInterfaceType();
10711071
auto *expr = param->getTypeCheckedDefaultExpr();
1072+
(void)param->getDefaultArgumentIsolation();
1073+
10721074
if (!ifacety->hasPlaceholder()) {
10731075
continue;
10741076
}
@@ -1149,13 +1151,22 @@ Expr *DefaultArgumentExprRequest::evaluate(Evaluator &evaluator,
11491151
// Walk the checked initializer and contextualize any closures
11501152
// we saw there.
11511153
TypeChecker::contextualizeInitializer(dc, initExpr);
1152-
1153-
checkInitializerActorIsolation(dc, initExpr);
11541154
TypeChecker::checkInitializerEffects(dc, initExpr);
11551155

11561156
return initExpr;
11571157
}
11581158

1159+
ActorIsolation
1160+
DefaultArgumentIsolation::evaluate(Evaluator &evaluator,
1161+
ParamDecl *param) const {
1162+
if (!param->hasDefaultExpr())
1163+
return ActorIsolation::forUnspecified();
1164+
1165+
auto *dc = param->getDefaultArgumentInitContext();
1166+
auto *initExpr = param->getTypeCheckedDefaultExpr();
1167+
return computeRequiredIsolation(dc, initExpr);
1168+
}
1169+
11591170
Type DefaultArgumentTypeRequest::evaluate(Evaluator &evaluator,
11601171
ParamDecl *param) const {
11611172
if (auto *expr = param->getTypeCheckedDefaultExpr()) {

0 commit comments

Comments
 (0)