Skip to content

[Concurrency] Allow default arguments to require actor isolation. #68794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2273,7 +2273,9 @@ class PatternBindingDecl final : public Decl,
void setInitializerSubsumed(unsigned i) {
getMutablePatternList()[i].setInitializerSubsumed();
}


ActorIsolation getInitializerIsolation(unsigned i) const;

/// Does this binding declare something that requires storage?
bool hasStorage() const;

Expand Down Expand Up @@ -5973,6 +5975,19 @@ class VarDecl : public AbstractStorageDecl {
return getParentExecutableInitializer() != nullptr;
}

/// Get the required actor isolation for evaluating the initializer
/// expression synchronously (if there is one).
///
/// If this VarDecl is a stored instance property, the initializer
/// can only be used in an `init` that meets the required isolation.
/// Otherwise, the property must be explicitly initialized in the `init`.
///
/// If this is a ParamDecl, the initializer isolation is required at
/// the call-site in order to use the default argument for this parameter.
/// If the required isolation is not met, an argument must be written
/// explicitly at the call-site.
ActorIsolation getInitializerIsolation() const;

// Return whether this VarDecl has an initial value, either by checking
// if it has an initializer in its parent pattern binding or if it has
// the @_hasInitialValue attribute.
Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5236,6 +5236,13 @@ ERROR(distributed_actor_isolated_non_self_reference,none,
"distributed actor-isolated %kind0 can not be accessed from a "
"non-isolated context",
(const ValueDecl *))
ERROR(isolated_default_argument,none,
"%0 default argument cannot be synchronously evaluated from a "
"%1 context",
(ActorIsolation, ActorIsolation))
ERROR(conflicting_default_argument_isolation,none,
"default argument cannot be both %0 and %1",
(ActorIsolation, ActorIsolation))
ERROR(distributed_actor_needs_explicit_distributed_import,none,
"'Distributed' module not imported, required for 'distributed actor'",
())
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -4569,6 +4569,11 @@ class DefaultArgumentExpr final : public Expr {
/// expression within the context of the call site.
Expr *getCallerSideDefaultExpr() const;

/// Get the required actor isolation for evaluating this default argument
/// synchronously. If the caller does not meet the required isolation, the
/// argument must be written explicitly at the call-site.
ActorIsolation getRequiredIsolation() const;

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::DefaultArgument;
}
Expand Down
21 changes: 21 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -2934,6 +2934,27 @@ class DefaultArgumentTypeRequest
void cacheResult(Type type) const;
};

/// Compute the actor isolation needed to synchronously evaluate the
/// default initializer expression.
class DefaultInitializerIsolation
: public SimpleRequest<DefaultInitializerIsolation,
ActorIsolation(VarDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

ActorIsolation evaluate(Evaluator &evaluator,
VarDecl *) const;

public:
bool isCached() const { return true; }
};

void simple_display(llvm::raw_ostream &out, Initializer *init);

/// Computes the fully type-checked caller-side default argument within the
/// context of the call site that it will be inserted into.
class CallerSideDefaultArgExprRequest
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ SWIFT_REQUEST(TypeChecker, DefaultArgumentExprRequest,
Expr *(ParamDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DefaultArgumentTypeRequest,
Type(ParamDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DefaultInitializerIsolation,
ActorIsolation(VarDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DefaultArgumentInitContextRequest,
Initializer *(ParamDecl *), SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DefaultDefinitionTypeRequest,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ EXPERIMENTAL_FEATURE(SendNonSendable, false)
/// Within strict concurrency, narrow global variable isolation requirements.
EXPERIMENTAL_FEATURE(GlobalConcurrency, false)

/// Allow default arguments to require isolation at the call-site.
EXPERIMENTAL_FEATURE(IsolatedDefaultArguments, false)

/// Enable extended callbacks (with additional parameters) to be used when the
/// "playground transform" is enabled.
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3551,6 +3551,10 @@ static bool usesFeatureSendNonSendable(Decl *decl) {

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

static bool usesFeatureIsolatedDefaultArguments(Decl *decl) {
return false;
}

static bool usesFeaturePlaygroundExtendedCallbacks(Decl *decl) {
return false;
}
Expand Down
35 changes: 34 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2088,6 +2088,14 @@ bool PatternBindingDecl::isAsyncLet() const {
return false;
}

ActorIsolation
PatternBindingDecl::getInitializerIsolation(unsigned i) const {
auto *var = getPatternList()[i].getAnchoringVarDecl();
if (!var)
return ActorIsolation::forUnspecified();

return var->getInitializerIsolation();
}

bool PatternBindingDecl::hasStorage() const {
// Walk the pattern, to check to see if any of the VarDecls included in it
Expand Down Expand Up @@ -7127,6 +7135,14 @@ Expr *VarDecl::getParentExecutableInitializer() const {
return nullptr;
}

ActorIsolation VarDecl::getInitializerIsolation() const {
auto *mutableThis = const_cast<VarDecl *>(this);
return evaluateOrDefault(
getASTContext().evaluator,
DefaultInitializerIsolation{mutableThis},
ActorIsolation::forUnspecified());
}

SourceRange VarDecl::getSourceRange() const {
if (auto Param = dyn_cast<ParamDecl>(this))
return Param->getSourceRange();
Expand Down Expand Up @@ -10445,8 +10461,25 @@ ActorIsolation swift::getActorIsolationOfContext(
if (auto *vd = dyn_cast_or_null<ValueDecl>(dcToUse->getAsDecl()))
return getActorIsolation(vd);

if (auto *var = dcToUse->getNonLocalVarDecl())
// In the context of the initializing or default-value expression of a
// stored property, the isolation varies between instance and type members:
// - For a static stored property, the isolation matches the VarDecl.
// Static properties are initialized upon first use, so the isolation
// of the initializer must match the isolation required to access the
// property.
// - For a field of a nominal type, the expression can require a specific
// actor isolation. That default expression may only be used from inits
// that meet the required isolation.
if (auto *var = dcToUse->getNonLocalVarDecl()) {
auto &ctx = dc->getASTContext();
if (ctx.LangOpts.hasFeature(Feature::IsolatedDefaultArguments) &&
var->isInstanceMember() &&
!var->getAttrs().hasAttribute<LazyAttr>()) {
return ActorIsolation::forNonisolated();
}

return getActorIsolation(var);
}

if (auto *closure = dyn_cast<AbstractClosureExpr>(dcToUse)) {
return getClosureActorIsolation(closure);
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1606,6 +1606,11 @@ Expr *DefaultArgumentExpr::getCallerSideDefaultExpr() const {
new (ctx) ErrorExpr(getSourceRange(), getType()));
}

ActorIsolation
DefaultArgumentExpr::getRequiredIsolation() const {
return getParamDecl()->getInitializerIsolation();
}

ValueDecl *ApplyExpr::getCalledValue(bool skipFunctionConversions) const {
return ::getCalledValue(Fn, skipFunctionConversions);
}
Expand Down
18 changes: 18 additions & 0 deletions lib/AST/TypeCheckRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,24 @@ void DefaultArgumentTypeRequest::cacheResult(Type type) const {
param->setDefaultExprType(type);
}

//----------------------------------------------------------------------------//
// DefaultInitializerIsolation computation.
//----------------------------------------------------------------------------//

void swift::simple_display(llvm::raw_ostream &out, Initializer *init) {
switch (init->getInitializerKind()) {
case InitializerKind::PatternBinding:
out << "pattern binding initializer";
break;
case InitializerKind::DefaultArgument:
out << "default argument initializer";
break;
case InitializerKind::PropertyWrapper:
out << "property wrapper initializer";
break;
}
}

//----------------------------------------------------------------------------//
// CallerSideDefaultArgExprRequest computation.
//----------------------------------------------------------------------------//
Expand Down
21 changes: 21 additions & 0 deletions lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,27 @@ void SILGenFunction::emitMemberInitializer(DeclContext *dc, VarDecl *selfDecl,
if (!init)
continue;

// Member initializer expressions are only used in a constructor with
// matching actor isolation. If the isolation prohibits the member
// initializer from being evaluated synchronously (or propagating required
// isolation through closure bodies), then the default value cannot be used
// and the member must be explicitly initialized in the constructor.
auto *var = field->getAnchoringVarDecl(i);
auto requiredIsolation = var->getInitializerIsolation();
auto contextIsolation = getActorIsolationOfContext(dc);
switch (requiredIsolation) {
// 'nonisolated' expressions can be evaluated from anywhere
case ActorIsolation::Unspecified:
case ActorIsolation::Nonisolated:
break;

case ActorIsolation::GlobalActor:
case ActorIsolation::GlobalActorUnsafe:
case ActorIsolation::ActorInstance:
if (requiredIsolation != contextIsolation)
continue;
}

auto *varPattern = field->getPattern(i);

// Cleanup after this initialization.
Expand Down
Loading