diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 24309220b9f56..87631f3000eb9 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -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; @@ -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. diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 466a8685a2c83..2cbef41a8887c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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'", ()) diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 59d8ea8646919..d9a15c53a8724 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -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; } diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 118fd02e612b6..91a01856dd8c7 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -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 { +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 diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index bd89936fcaa15..dc3228d61f630 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -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, diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 8b40891b95553..cc79cde564aaf 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -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) diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 560403536edfb..af3f866058df1 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -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; } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index fab1c48621352..1acd3dcb01216 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -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 @@ -7127,6 +7135,14 @@ Expr *VarDecl::getParentExecutableInitializer() const { return nullptr; } +ActorIsolation VarDecl::getInitializerIsolation() const { + auto *mutableThis = const_cast(this); + return evaluateOrDefault( + getASTContext().evaluator, + DefaultInitializerIsolation{mutableThis}, + ActorIsolation::forUnspecified()); +} + SourceRange VarDecl::getSourceRange() const { if (auto Param = dyn_cast(this)) return Param->getSourceRange(); @@ -10445,8 +10461,25 @@ ActorIsolation swift::getActorIsolationOfContext( if (auto *vd = dyn_cast_or_null(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()) { + return ActorIsolation::forNonisolated(); + } + return getActorIsolation(var); + } if (auto *closure = dyn_cast(dcToUse)) { return getClosureActorIsolation(closure); diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 0db9de12dda6f..d854759dcb7bc 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -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); } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index aac2824c41d00..9660506ece0bf 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -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. //----------------------------------------------------------------------------// diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index cb6fa6f7a098f..031b0a5428d8c 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -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. diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index de0ad395b3469..ee603d53c4643 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -1939,6 +1939,12 @@ namespace { llvm::function_ref getClosureActorIsolation; + SourceLoc requiredIsolationLoc; + + /// Used under the mode to compute required actor isolation for + /// an expression or function. + llvm::SmallDenseMap requiredIsolation; + /// Keeps track of the capture context of variables that have been /// explicitly captured in closures. llvm::SmallDenseMap> @@ -2123,6 +2129,94 @@ namespace { } } + bool refineRequiredIsolation(ActorIsolation refinedIsolation) { + if (requiredIsolationLoc.isInvalid()) + return false; + + auto infersIsolationFromContext = + [](const DeclContext *dc) -> bool { + // Isolation for declarations is based solely on explicit + // annotations; only infer isolation for initializer expressions + // and closures. + if (dc->getAsDecl()) + return false; + + if (auto *closure = dyn_cast(dc)) { + // We cannot infer a more specific actor isolation for a Sendable + // closure. It is an error to cast away actor isolation from a + // function type, but this is okay for non-Sendable closures + // because they cannot leave the isolation domain they're created + // in anyway. + if (closure->isSendable()) + return false; + + if (closure->getActorIsolation().isActorIsolated()) + return false; + } + + return true; + }; + + // For the call to require the given actor isolation, every DeclContext + // in the current context stack must require the same isolation. If + // along the way to the innermost context, we find a DeclContext that + // has a different isolation (e.g. it's a local function that does not + // recieve isolation from its decl context), then the expression cannot + // require a different isolation. + for (auto *dc : contextStack) { + if (!infersIsolationFromContext(dc)) + return false; + + // To refine the required isolation, the existing requirement + // must either be 'nonisolated' or exactly the same as the + // new refinement. + auto isolation = requiredIsolation.find(dc); + if (isolation == requiredIsolation.end() || + isolation->second == ActorIsolation::Nonisolated) { + requiredIsolation[dc] = refinedIsolation; + } else if (isolation->second != refinedIsolation) { + dc->getASTContext().Diags.diagnose( + requiredIsolationLoc, + diag::conflicting_default_argument_isolation, + isolation->second, refinedIsolation); + return true; + } + } + + return true; + } + + void checkDefaultArgument(DefaultArgumentExpr *expr) { + // Check the context isolation against the required isolation for + // evaluating the default argument synchronously. If the default + // argument must be evaluated asynchronously, it must be written + // explicitly in the argument list with 'await'. + auto requiredIsolation = expr->getRequiredIsolation(); + auto contextIsolation = getInnermostIsolatedContext( + getDeclContext(), getClosureActorIsolation); + + if (requiredIsolation == contextIsolation) + return; + + switch (requiredIsolation) { + // Nonisolated is okay from any caller isolation because + // default arguments cannot have any async calls. + case ActorIsolation::Unspecified: + case ActorIsolation::Nonisolated: + return; + + case ActorIsolation::GlobalActor: + case ActorIsolation::GlobalActorUnsafe: + case ActorIsolation::ActorInstance: + break; + } + + auto &ctx = getDeclContext()->getASTContext(); + ctx.Diags.diagnose( + expr->getLoc(), diag::isolated_default_argument, + requiredIsolation, contextIsolation); + } + /// Check closure captures for Sendable violations. void checkLocalCaptures(AnyFunctionRef localFunc) { SmallVector captures; @@ -2180,6 +2274,17 @@ namespace { contextStack.push_back(dc); } + ActorIsolation computeRequiredIsolation(Expr *expr) { + auto &ctx = getDeclContext()->getASTContext(); + + if (ctx.LangOpts.hasFeature(Feature::IsolatedDefaultArguments)) + requiredIsolationLoc = expr->getLoc(); + + expr->walk(*this); + requiredIsolationLoc = SourceLoc(); + return requiredIsolation[getDeclContext()]; + } + /// Searches the applyStack from back to front for the inner-most CallExpr /// and marks that CallExpr as implicitly async. /// @@ -2375,6 +2480,10 @@ namespace { checkFunctionConversion(funcConv); } + if (auto *defaultArg = dyn_cast(expr)) { + checkDefaultArgument(defaultArg); + } + return Action::Continue(expr); } @@ -2920,6 +3029,9 @@ namespace { if (!unsatisfiedIsolation) return false; + if (refineRequiredIsolation(*unsatisfiedIsolation)) + return false; + // At this point, we know a jump is made to the callee that yields // an isolation requirement unsatisfied by the calling context, so // set the unsatisfiedIsolationJump fields of the ApplyExpr appropriately @@ -3255,6 +3367,14 @@ namespace { case AsyncMarkingResult::SyncContext: case AsyncMarkingResult::NotFound: + // If we found an implicitly async reference in a sync + // context and we're computing the required isolation for + // an expression, the calling context requires the isolation + // of the reference. + if (refineRequiredIsolation(result.isolation)) { + return false; + } + // Complain about access outside of the isolation domain. auto useKind = static_cast( kindOfUsage(decl, context).value_or(VarRefUseEnv::Read)); @@ -3462,11 +3582,6 @@ void swift::checkFunctionActorIsolation(AbstractFunctionDecl *decl) { } } -void swift::checkInitializerActorIsolation(Initializer *init, Expr *expr) { - ActorIsolationChecker checker(init); - expr->walk(checker); -} - void swift::checkEnumElementActorIsolation( EnumElementDecl *element, Expr *expr) { ActorIsolationChecker checker(element); @@ -4497,6 +4612,48 @@ bool HasIsolatedSelfRequest::evaluate( return true; } +ActorIsolation +DefaultInitializerIsolation::evaluate(Evaluator &evaluator, + VarDecl *var) const { + if (var->isInvalid()) + return ActorIsolation::forUnspecified(); + + Initializer *dc = nullptr; + Expr *initExpr = nullptr; + + if (auto *pbd = var->getParentPatternBinding()) { + if (!var->isParentInitialized()) + return ActorIsolation::forUnspecified(); + + auto i = pbd->getPatternEntryIndexForVarDecl(var); + dc = cast(pbd->getInitContext(i)); + initExpr = var->getParentInitializer(); + } else if (auto *param = dyn_cast(var)) { + // If this parameter corresponds to a stored property for a + // memberwise initializer, the default argument is the default + // initializer expression. + if (auto *property = param->getStoredProperty()) { + // FIXME: Force computation of property wrapper initializers. + if (auto *wrapped = property->getOriginalWrappedProperty()) + (void)property->getPropertyWrapperInitializerInfo(); + + return property->getInitializerIsolation(); + } + + if (!param->hasDefaultExpr()) + return ActorIsolation::forUnspecified(); + + dc = param->getDefaultArgumentInitContext(); + initExpr = param->getTypeCheckedDefaultExpr(); + } + + if (!dc || !initExpr) + return ActorIsolation::forUnspecified(); + + ActorIsolationChecker checker(dc); + return checker.computeRequiredIsolation(initExpr); +} + void swift::checkOverrideActorIsolation(ValueDecl *value) { if (isa(value)) return; diff --git a/lib/Sema/TypeCheckConcurrency.h b/lib/Sema/TypeCheckConcurrency.h index 3947fa1d3b6a2..40a01dde168ad 100644 --- a/lib/Sema/TypeCheckConcurrency.h +++ b/lib/Sema/TypeCheckConcurrency.h @@ -56,7 +56,6 @@ void addAsyncNotes(AbstractFunctionDecl const* func); /// Check actor isolation rules. void checkTopLevelActorIsolation(TopLevelCodeDecl *decl); void checkFunctionActorIsolation(AbstractFunctionDecl *decl); -void checkInitializerActorIsolation(Initializer *init, Expr *expr); void checkEnumElementActorIsolation(EnumElementDecl *element, Expr *expr); void checkPropertyWrapperActorIsolation(VarDecl *wrappedVar, Expr *expr); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index bbe04d408cb1f..628c846c92be3 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -1069,6 +1069,8 @@ static void checkDefaultArguments(ParameterList *params) { for (auto *param : *params) { auto ifacety = param->getInterfaceType(); auto *expr = param->getTypeCheckedDefaultExpr(); + (void)param->getInitializerIsolation(); + if (!ifacety->hasPlaceholder()) { continue; } @@ -1149,8 +1151,6 @@ Expr *DefaultArgumentExprRequest::evaluate(Evaluator &evaluator, // Walk the checked initializer and contextualize any closures // we saw there. TypeChecker::contextualizeInitializer(dc, initExpr); - - checkInitializerActorIsolation(dc, initExpr); TypeChecker::checkInitializerEffects(dc, initExpr); return initExpr; @@ -2466,7 +2466,7 @@ class DeclChecker : public DeclVisitor { PBD->getInitContext(i)); if (initContext) { TypeChecker::contextualizeInitializer(initContext, init); - checkInitializerActorIsolation(initContext, init); + (void)PBD->getInitializerIsolation(i); TypeChecker::checkInitializerEffects(initContext, init); } } diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index d5ed89f6b428b..3deddf2eb4533 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -3240,7 +3240,7 @@ PropertyWrapperInitializerInfoRequest::evaluate(Evaluator &evaluator, // Check initializer effects. auto *initContext = new (ctx) PropertyWrapperInitializer( dc, param, PropertyWrapperInitializer::Kind::ProjectedValue); - checkInitializerActorIsolation(initContext, projectedValueInit); + (void)projection->getInitializerIsolation(); TypeChecker::checkInitializerEffects(initContext, projectedValueInit); } } diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 76015325ac4ab..40d18e78e43d0 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -455,6 +455,22 @@ getActualDefaultArgKind(uint8_t raw) { return llvm::None; } +static llvm::Optional +getActualActorIsolationKind(uint8_t raw) { + switch (static_cast(raw)) { +#define CASE(X) \ + case serialization::ActorIsolation::X: \ + return swift::ActorIsolation::Kind::X; + CASE(Unspecified) + CASE(ActorInstance) + CASE(Nonisolated) + CASE(GlobalActor) + CASE(GlobalActorUnsafe) +#undef CASE + } + return llvm::None; +} + static llvm::Optional getActualClangDeclPathComponentKind(uint64_t raw) { switch (static_cast(raw)) { @@ -3764,6 +3780,8 @@ class DeclDeserializer { bool isCompileTimeConst; uint8_t rawDefaultArg; TypeID defaultExprType; + uint8_t rawDefaultArgIsolation; + TypeID globalActorTypeID; decls_block::ParamLayout::readRecord(scratch, argNameID, paramNameID, contextID, rawSpecifier, @@ -3771,7 +3789,9 @@ class DeclDeserializer { isAutoClosure, isIsolated, isCompileTimeConst, rawDefaultArg, - defaultExprType); + defaultExprType, + rawDefaultArgIsolation, + globalActorTypeID); auto argName = MF.getIdentifier(argNameID); auto paramName = MF.getIdentifier(paramNameID); @@ -3816,6 +3836,29 @@ class DeclDeserializer { if (auto exprType = MF.getType(defaultExprType)) param->setDefaultExprType(exprType); + auto isoKind = *getActualActorIsolationKind(rawDefaultArgIsolation); + auto globalActor = MF.getType(globalActorTypeID); + ActorIsolation isolation; + switch (isoKind) { + case ActorIsolation::Unspecified: + case ActorIsolation::Nonisolated: + isolation = ActorIsolation::forUnspecified(); + break; + + case ActorIsolation::GlobalActor: + case ActorIsolation::GlobalActorUnsafe: + isolation = ActorIsolation::forGlobalActor( + globalActor, isoKind == ActorIsolation::GlobalActorUnsafe); + break; + + case ActorIsolation::ActorInstance: + llvm_unreachable("default arg cannot be actor instance isolated"); + } + + ctx.evaluator.cacheOutput( + DefaultInitializerIsolation{param}, + std::move(isolation)); + if (!blobData.empty()) param->setDefaultValueStringRepresentation(blobData); } diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index c514bb45259c3..9b58cdf2a8e06 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 810; // access enforcement +const uint16_t SWIFTMODULE_VERSION_MINOR = 811; // default argument isolation /// A standard hash seed used for all string hashes in a serialized module. /// @@ -526,6 +526,17 @@ enum class DefaultArgumentKind : uint8_t { }; using DefaultArgumentField = BCFixed<4>; +/// These IDs must \em not be renumbered or reordered without incrementing +/// the module version. +enum class ActorIsolation : uint8_t { + Unspecified = 0, + ActorInstance, + Nonisolated, + GlobalActor, + GlobalActorUnsafe +}; +using ActorIsolationField = BCFixed<3>; + // These IDs must \em not be renumbered or reordered without incrementing // the module version. enum LibraryKind : uint8_t { @@ -1568,6 +1579,8 @@ namespace decls_block { BCFixed<1>, // isCompileTimeConst? DefaultArgumentField, // default argument kind TypeIDField, // default argument type + ActorIsolationField, // default argument isolation + TypeIDField, // global actor isolation BCBlob // default argument text >; diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index fc4393b45d50c..a1f447d8ed246 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1392,6 +1392,22 @@ static uint8_t getRawStableDefaultArgumentKind(swift::DefaultArgumentKind kind) llvm_unreachable("Unhandled DefaultArgumentKind in switch."); } +static uint8_t +getRawStableActorIsolationKind(swift::ActorIsolation::Kind kind) { + switch (kind) { +#define CASE(X) \ + case swift::ActorIsolation::X: \ + return static_cast(serialization::ActorIsolation::X); + CASE(Unspecified) + CASE(ActorInstance) + CASE(Nonisolated) + CASE(GlobalActor) + CASE(GlobalActorUnsafe) +#undef CASE + } + llvm_unreachable("bad actor isolation"); +} + static uint8_t getRawStableMetatypeRepresentation(const AnyMetatypeType *metatype) { if (!metatype->hasRepresentation()) { @@ -4304,6 +4320,11 @@ class Serializer::DeclSerializer : public DeclVisitor { defaultExprType = param->getTypeOfDefaultExpr(); } + auto isolation = param->getInitializerIsolation(); + Type globalActorType; + if (isolation.isGlobalActor()) + globalActorType = isolation.getGlobalActor(); + unsigned abbrCode = S.DeclTypeAbbrCodes[ParamLayout::Code]; ParamLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode, S.addDeclBaseNameRef(param->getArgumentName()), @@ -4318,6 +4339,8 @@ class Serializer::DeclSerializer : public DeclVisitor { param->isCompileTimeConst(), getRawStableDefaultArgumentKind(argKind), S.addTypeRef(defaultExprType), + getRawStableActorIsolationKind(isolation.getKind()), + S.addTypeRef(globalActorType), defaultArgumentText); if (interfaceType->hasError() && !S.allowCompilerErrors()) { diff --git a/test/Concurrency/Inputs/serialized_default_arguments.swift b/test/Concurrency/Inputs/serialized_default_arguments.swift new file mode 100644 index 0000000000000..8035135ec78d6 --- /dev/null +++ b/test/Concurrency/Inputs/serialized_default_arguments.swift @@ -0,0 +1,11 @@ + +@MainActor +public func mainActorDefaultArg() -> Int { 0 } + +@MainActor +public func useMainActorDefault(_ value: Int = mainActorDefaultArg()) {} + +public func nonisolatedDefaultArg() -> Int { 0 } + +@MainActor +public func useNonisolatedDefault(_ value: Int = nonisolatedDefaultArg()) {} diff --git a/test/Concurrency/isolated_default_arguments.swift b/test/Concurrency/isolated_default_arguments.swift new file mode 100644 index 0000000000000..ba092542f06fe --- /dev/null +++ b/test/Concurrency/isolated_default_arguments.swift @@ -0,0 +1,187 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking + +// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -enable-experimental-feature IsolatedDefaultArguments -parse-as-library -emit-sil -o /dev/null -verify %s +// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-experimental-feature IsolatedDefaultArguments -enable-experimental-feature SendNonSendable %s + +// REQUIRES: concurrency +// REQUIRES: asserts + +@globalActor +actor SomeGlobalActor { + static let shared = SomeGlobalActor() +} + +// expected-note@+2 2 {{calls to global function 'requiresMainActor()' from outside of its actor context are implicitly asynchronous}} +@MainActor +func requiresMainActor() -> Int { 0 } + +@SomeGlobalActor +func requiresSomeGlobalActor() -> Int { 0 } + +func mainActorDefaultArg(value: Int = requiresMainActor()) {} + +func mainActorClosure( + closure: () -> Int = { + requiresMainActor() + } +) {} + +func mainActorClosureCall( + closure: Int = { + requiresMainActor() + }() +) {} + +@MainActor func mainActorCaller() { + mainActorDefaultArg() + mainActorClosure() + mainActorClosureCall() +} + +func nonisolatedCaller() { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorDefaultArg() + + // FIXME: confusing error message. + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorClosure() + + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorClosureCall() +} + +func nonisolatedAsyncCaller() async { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorDefaultArg() + + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorClosure() + + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + mainActorClosureCall() + + await mainActorDefaultArg(value: requiresMainActor()) + + // 'await' doesn't help closures in default arguments; the calling context needs a matching + // actor isolation for that isolation to be inferred for the closure value. + + // expected-error@+2 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + // expected-warning@+1 {{no 'async' operations occur within 'await' expression}} + await mainActorClosure() + + // expected-error@+2 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + // expected-warning@+1 {{no 'async' operations occur within 'await' expression}} + await mainActorClosureCall() +} + +func conflictingIsolationDefaultArg( + // expected-error@+1 {{default argument cannot be both main actor-isolated and global actor 'SomeGlobalActor'-isolated}} + value: (Int, Int) = (requiresMainActor(), requiresSomeGlobalActor()) +) {} + +func closureLosesIsolation( + closure: @Sendable () -> Void = { + // expected-note@+1 {{calls to local function 'f()' from outside of its actor context are implicitly asynchronous}} + @MainActor func f() {} + // expected-error@+1 {{call to main actor-isolated local function 'f()' in a synchronous nonisolated context}} + f() + } +) {} + +func closureIsolationMismatch( + closure: @SomeGlobalActor () -> Void = { + // expected-note@+1 {{calls to local function 'f()' from outside of its actor context are implicitly asynchronous}} + @MainActor func f() {} + // expected-error@+1 {{call to main actor-isolated local function 'f()' in a synchronous global actor 'SomeGlobalActor'-isolated context}} + f() + } +) {} + +func conflictingClosureIsolation( + // expected-error@+1 {{default argument cannot be both main actor-isolated and global actor 'SomeGlobalActor'-isolated}} + closure: () -> Void = { + _ = requiresMainActor() + _ = requiresSomeGlobalActor() + } +) {} + +func isolationInLocalFunction( + closure: () -> Void = { + nonisolated func local() -> Int { + // expected-error@+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous nonisolated context}} + requiresMainActor() + } + } +) {} + +actor A {} + +func closureWithIsolatedParam( + closure: (isolated A) -> Void = { _ in + // expected-error@+1 {{call to main actor-isolated global function 'requiresMainActor()' in a synchronous actor-isolated context}} + _ = requiresMainActor() + } +) {} + +@MainActor +struct S1 { + var required: Int + + var x: Int = requiresMainActor() + + lazy var y: Int = requiresMainActor() + + static var z: Int = requiresMainActor() +} + +@SomeGlobalActor +struct S2 { + var required: Int + + var x: Int = requiresSomeGlobalActor() + + lazy var y: Int = requiresSomeGlobalActor() + + static var z: Int = requiresSomeGlobalActor() +} + +struct S3 { + // expected-error@+1 {{default argument cannot be both main actor-isolated and global actor 'SomeGlobalActor'-isolated}} + var (x, y, z) = (requiresMainActor(), requiresSomeGlobalActor(), 10) +} + +@MainActor +func initializeFromMainActor() { + _ = S1(required: 10) + + // expected-error@+1 {{global actor 'SomeGlobalActor'-isolated default argument cannot be synchronously evaluated from a main actor-isolated context}} + _ = S2(required: 10) +} + +@SomeGlobalActor +func initializeFromSomeGlobalActor() { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a global actor 'SomeGlobalActor'-isolated context}} + _ = S1(required: 10) + + _ = S2(required: 10) +} + +func initializeFromNonisolated() { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + _ = S1(required: 10) + + // expected-error@+1 {{global actor 'SomeGlobalActor'-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + _ = S2(required: 10) +} + +extension A { + func initializeFromActorInstance() { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a actor-isolated context}} + _ = S1(required: 10) + + // expected-error@+1 {{global actor 'SomeGlobalActor'-isolated default argument cannot be synchronously evaluated from a actor-isolated context}} + _ = S2(required: 10) + } +} diff --git a/test/Concurrency/isolated_default_arguments_serialized.swift b/test/Concurrency/isolated_default_arguments_serialized.swift new file mode 100644 index 0000000000000..6f228693e8103 --- /dev/null +++ b/test/Concurrency/isolated_default_arguments_serialized.swift @@ -0,0 +1,25 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -swift-version 5 -emit-module-path %t/SerializedDefaultArguments.swiftmodule -module-name SerializedDefaultArguments -enable-experimental-feature IsolatedDefaultArguments %S/Inputs/serialized_default_arguments.swift + +// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -disable-availability-checking -swift-version 6 -I %t +// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -disable-availability-checking -swift-version 6 -I %t -enable-experimental-feature SendNonSendable -enable-experimental-feature IsolatedDefaultArguments + +// REQUIRES: concurrency +// REQUIRES: asserts + +import SerializedDefaultArguments + +@MainActor +func mainActorCaller() { + useMainActorDefault() + useNonisolatedDefault() +} + +func nonisolatedCaller() async { + // expected-error@+1 {{main actor-isolated default argument cannot be synchronously evaluated from a nonisolated context}} + await useMainActorDefault() + + await useMainActorDefault(mainActorDefaultArg()) + + await useNonisolatedDefault() +} diff --git a/test/Concurrency/isolated_default_property_inits.swift b/test/Concurrency/isolated_default_property_inits.swift new file mode 100644 index 0000000000000..aae65d9ee0fbe --- /dev/null +++ b/test/Concurrency/isolated_default_property_inits.swift @@ -0,0 +1,57 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking + +// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -enable-experimental-feature IsolatedDefaultArguments -parse-as-library -emit-sil -o /dev/null -verify %s +// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-experimental-feature IsolatedDefaultArguments -enable-experimental-feature SendNonSendable %s + +// REQUIRES: concurrency +// REQUIRES: asserts + +@globalActor +actor SomeGlobalActor { + static let shared = SomeGlobalActor() +} + +@MainActor +func requiresMainActor() -> Int { 0 } + +@SomeGlobalActor +func requiresSomeGlobalActor() -> Int { 0 } + +struct S1 { + // expected-note@+1 2 {{'self.x' not initialized}} + var x = requiresMainActor() + // expected-note@+1 2 {{'self.y' not initialized}} + var y = requiresSomeGlobalActor() + var z = 10 + + // expected-error@+1 {{return from initializer without initializing all stored properties}} + nonisolated init(a: Int) {} + + // expected-error@+1 {{return from initializer without initializing all stored properties}} + @MainActor init(b: Int) {} + + // expected-error@+1 {{return from initializer without initializing all stored properties}} + @SomeGlobalActor init(c: Int) {} +} + +struct S2 { + var x = requiresMainActor() + var y = requiresSomeGlobalActor() + var z = 10 + + nonisolated init(x: Int, y: Int) { + self.x = x + self.y = y + } + + @MainActor init(y: Int) { + self.y = y + } + + @SomeGlobalActor init(x: Int) { + self.x = x + } +} +