diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4352c0c72d1e3..33340a34ff1c7 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6029,6 +6029,13 @@ ERROR(global_actor_not_usable_from_inline,none, NOTE(move_global_actor_attr_to_storage_decl,none, "move global actor attribute to %kind0", (const ValueDecl *)) +ERROR(default_isolation_internal,none, + "default isolation can only be set per file", + ()) +ERROR(default_isolation_custom,none, + "default isolation can only be set to 'MainActor' or 'nonisolated'", + ()) + ERROR(actor_isolation_multiple_attr_2,none, "%kind0 has multiple actor-isolation attributes (%1 and %2)", (const Decl *, DeclAttribute, DeclAttribute)) diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 73e3b44485598..5d8cf87114255 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -74,6 +74,7 @@ IDENTIFIER(decodeIfPresent) IDENTIFIER(Decoder) IDENTIFIER(decoder) IDENTIFIER(DefaultDistributedActorSystem) +IDENTIFIER(DefaultIsolation) IDENTIFIER_(Differentiation) IDENTIFIER_WITH_NAME(PatternMatchVar, "$match") IDENTIFIER(dynamicallyCall) diff --git a/include/swift/AST/KnownSDKTypes.def b/include/swift/AST/KnownSDKTypes.def index ff18654c43cd3..4296f25cf7df6 100644 --- a/include/swift/AST/KnownSDKTypes.def +++ b/include/swift/AST/KnownSDKTypes.def @@ -40,6 +40,7 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0) KNOWN_SDK_TYPE_DECL(Concurrency, CheckedContinuation, NominalTypeDecl, 2) KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2) KNOWN_SDK_TYPE_DECL(Concurrency, MainActor, NominalTypeDecl, 0) +KNOWN_SDK_TYPE_DECL(Concurrency, nonisolated, TypeAliasDecl, 0) KNOWN_SDK_TYPE_DECL(Concurrency, Job, StructDecl, 0) // legacy type; prefer ExecutorJob KNOWN_SDK_TYPE_DECL(Concurrency, ExecutorJob, StructDecl, 0) KNOWN_SDK_TYPE_DECL(Concurrency, UnownedJob, StructDecl, 0) diff --git a/include/swift/AST/SourceFile.h b/include/swift/AST/SourceFile.h index 6b8e1b7accd93..77687bcf31dc3 100644 --- a/include/swift/AST/SourceFile.h +++ b/include/swift/AST/SourceFile.h @@ -20,6 +20,7 @@ #include "swift/AST/Import.h" #include "swift/AST/SynthesizedFileUnit.h" #include "swift/Basic/Debug.h" +#include "swift/Basic/LangOptions.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" @@ -58,9 +59,9 @@ using ImportAccessLevel = std::optional>; /// /// Vended by SourceFile::getLanguageOptions(). struct SourceFileLangOptions { - /// If unset, no value was provided. If a Type, that type is the type of the - /// isolation. If set to an empty type, nil was specified explicitly. - std::optional defaultIsolation; + /// The default actor isolation to infer on declarations + /// within the source file. + std::optional defaultIsolation; }; /// A file containing Swift source code. diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 112790754f961..e92d4c45ea1cb 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -33,6 +33,7 @@ #include "swift/AST/SourceFile.h" #include "swift/AST/Type.h" #include "swift/AST/TypeResolutionStage.h" +#include "swift/Basic/LangOptions.h" #include "swift/Basic/Statistic.h" #include "swift/Basic/TaggedUnion.h" #include "swift/Basic/TypeID.h" @@ -1621,6 +1622,25 @@ class ActorIsolationRequest : bool isCached() const { return true; } }; +/// Determine the default actor isolation for the given source file. +class DefaultActorIsolationRequest : + public SimpleRequest(SourceFile *), + RequestFlags::Cached> { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + std::optional + evaluate(Evaluator &evaluator, SourceFile *file) const; + +public: + // Caching + bool isCached() const { return true; } +}; + /// Determine whether the given function should have an isolated 'self'. class HasIsolatedSelfRequest : public SimpleRequest(SourceFile *), + Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, HasIsolatedSelfRequest, bool(ValueDecl *), Uncached, NoLocationInfo) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 153393a495a65..35eeebf945119 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -501,6 +501,9 @@ EXPERIMENTAL_FEATURE(InferIsolatedConformances, true) /// Allow SwiftSettings EXPERIMENTAL_FEATURE(SwiftSettings, false) +/// Enable setting default isolation per file via typealias. +EXPERIMENTAL_FEATURE(DefaultIsolationTypealias, true) + /// Syntax sugar features for concurrency. EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true) diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index e787264356c93..625643ec94f48 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -405,6 +405,7 @@ UNINTERESTING_FEATURE(ImportNonPublicCxxMembers) UNINTERESTING_FEATURE(CXXForeignReferenceTypeInitializers) UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError) UNINTERESTING_FEATURE(AllowRuntimeSymbolDeclarations) +UNINTERESTING_FEATURE(DefaultIsolationTypealias) static bool usesFeatureSwiftSettings(const Decl *decl) { // We just need to guard `#SwiftSettings`. diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index f8ed0c23c6850..1cf121a93b07d 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -4223,12 +4223,11 @@ struct SwiftSettingsWalker : ASTWalker { } /// Given a specific CallExpr, pattern matches the CallExpr's first argument - /// to validate it is MainActor.self. Returns CanType() if the CallExpr has - /// multiple parameters or if its first parameter is not a MainActor.self. + /// to validate it is MainActor.self. /// - /// This is used when pattern matching against - /// .defaultIsolation(MainActor.self). - CanType patternMatchDefaultIsolationMainActor(CallExpr *callExpr); + /// \returns \c true if the call has one argument that matches + /// \c MainActor.self, and \c false otherwise. + bool patternMatchDefaultIsolationMainActor(CallExpr *callExpr); /// Validates that macroExpr is a well formed "SwiftSettings" macro. Returns /// true if we can process it and false otherwise. @@ -4294,16 +4293,16 @@ struct SwiftSettingsWalker : ASTWalker { } // Otherwise, set things up appropriately. - if (auto actor = patternMatchDefaultIsolationMainActor(callExpr)) { + if (patternMatchDefaultIsolationMainActor(callExpr)) { expr = callExpr; - result.defaultIsolation = actor; + result.defaultIsolation = DefaultIsolation::MainActor; foundValidArg = true; continue; } if (isa(callExpr->getArgs()->getExpr(0))) { expr = callExpr; - result.defaultIsolation = {Type()}; + result.defaultIsolation = DefaultIsolation::Nonisolated; foundValidArg = true; continue; } @@ -4405,18 +4404,18 @@ SwiftSettingsWalker::getSwiftSettingArgDecl(Argument arg) { return {{callExpr, f}}; } -CanType +bool SwiftSettingsWalker::patternMatchDefaultIsolationMainActor(CallExpr *callExpr) { // Grab the dot self expr. auto *selfExpr = dyn_cast(callExpr->getArgs()->getExpr(0)); if (!selfExpr) - return CanType(); + return false; // Then validate we have something that is MainActor. auto *declRefExpr = dyn_cast(selfExpr->getSubExpr()); if (!declRefExpr || !declRefExpr->getName().getBaseName().getIdentifier().is("MainActor")) - return CanType(); + return false; // Then use unqualified lookup descriptor to find our MainActor. UnqualifiedLookupDescriptor lookupDesc{ @@ -4425,20 +4424,20 @@ SwiftSettingsWalker::patternMatchDefaultIsolationMainActor(CallExpr *callExpr) { auto lookup = evaluateOrDefault(ctx.evaluator, UnqualifiedLookupRequest{lookupDesc}, {}); if (lookup.allResults().empty()) - return CanType(); + return false; // Then grab our nominal type decl and make sure it is from the concurrency // module. auto *nomDecl = dyn_cast(lookup.allResults().front().getValueDecl()); if (!nomDecl) - return CanType(); + return false; auto *nomDeclDC = nomDecl->getDeclContext(); auto *nomDeclModule = nomDecl->getParentModule(); if (!nomDeclDC->isModuleScopeContext() || !nomDeclModule->isConcurrencyModule()) - return CanType(); + return false; - return nomDecl->getDeclaredType()->getCanonicalType(); + return true; } SourceFileLangOptions diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 006c6652f4eae..efdf29932de35 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -5732,6 +5732,75 @@ static void addAttributesForActorIsolation(ValueDecl *value, } } +std::optional +DefaultActorIsolationRequest::evaluate(Evaluator &evaluator, + SourceFile *sourceFile) const { + if (!sourceFile) + return std::nullopt; + + auto &ctx = sourceFile->getASTContext(); + auto defaultModuleIsolaton = ctx.LangOpts.DefaultIsolationBehavior; + auto mainActorType = ctx.getMainActorType(); + if (!mainActorType) + return defaultModuleIsolaton; + + if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) { + auto options = sourceFile->getLanguageOptions(); + if (auto isolation = options.defaultIsolation) { + return *isolation; + } + } + + if (ctx.LangOpts.hasFeature(Feature::DefaultIsolationTypealias)) { + auto nonisolatedType = ctx.getnonisolatedType(); + + auto decls = sourceFile->getTopLevelDecls(); + if (decls.empty()) + return defaultModuleIsolaton; + + auto locInFile = decls.front()->getStartLoc(); + auto defaultIsolationResult = TypeChecker::lookupUnqualified( + sourceFile->getModuleScopeContext(), + DeclNameRef(ctx.Id_DefaultIsolation), + locInFile); + for (auto found : defaultIsolationResult) { + auto *decl = found.getValueDecl(); + if (!decl) + continue; + + auto *typealias = dyn_cast(decl); + if (!typealias || + !typealias->getDeclContext()->isModuleScopeContext()) + continue; + + // We have a top-level 'DefaultIsolation' typealias. We can assume + // it's the only one, because multiple top-level typealiases with + // the same name is a redeclaration error, and default isolation + // cannot be set by an imported library. + + // The typealias can only be used to set default isolation per file. + if (typealias->getFormalAccess() >= AccessLevel::Internal) { + typealias->diagnose(diag::default_isolation_internal); + break; + } + + auto type = typealias->getUnderlyingType(); + if (type->isEqual(mainActorType)) + return DefaultIsolation::MainActor; + + if (type->isEqual(nonisolatedType)) + return DefaultIsolation::Nonisolated; + + // The underlying type of the typealias must either be 'MainActor' + // or 'nonisolated'. + typealias->diagnose(diag::default_isolation_custom); + break; + } + } + + return defaultModuleIsolaton; +} + /// Determine the default isolation and isolation source for this declaration, /// which may still be overridden by other inference rules. static std::tuplegetASTContext(); - // If we are supposed to infer main actor isolation by default for entities - // within our module, make our default isolation main actor. - if (value->getModuleContext() == ctx.MainModule) { - auto globalActorHelper = [&](Type globalActor) - -> std::optional>> { + // Determine whether default isolation is set to MainActor, either for + // the entire module or in this specific file. + auto mainActorType = ctx.getMainActorType(); + if (mainActorType && value->getModuleContext() == ctx.MainModule) { + // See if we have one specified by our file unit. + auto *sourceFile = value->getDeclContext()->getParentSourceFile(); + auto defaultIsolation = evaluateOrDefault( + ctx.evaluator, DefaultActorIsolationRequest{sourceFile}, + ctx.LangOpts.DefaultIsolationBehavior); + + if (defaultIsolation == DefaultIsolation::MainActor) { // Default global actor isolation does not apply to any declarations // within actors and distributed actors. bool inActorContext = false; @@ -5761,39 +5835,12 @@ computeDefaultInferredActorIsolation(ValueDecl *value) { if (isa(value) || isa(value) || isa(value) || isa(value) || isa(value)) { - return { - {{ActorIsolation::forGlobalActor(globalActor), {}}, nullptr, {}}}; - } - } - - return {}; - }; - - // Otherwise, see if we have one specified by our file unit. - bool ignoreUnspecifiedMeansMainActorIsolated = false; - if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) { - if (auto *sourceFile = value->getDeclContext()->getParentSourceFile()) { - auto options = sourceFile->getLanguageOptions(); - if (auto isolation = options.defaultIsolation) { - if (*isolation) { - auto result = globalActorHelper(*options.defaultIsolation); - if (result) - return *result; - } else { - // If we found a nil type, then we know we should ignore unspecified - // means main actor isolated. - ignoreUnspecifiedMeansMainActorIsolated = true; - } + auto mainActorIsolation = + ActorIsolation::forGlobalActor(mainActorType); + return {{mainActorIsolation, {}}, nullptr, {}}; } } } - - // If we are required to use main actor... just use that. - if (!ignoreUnspecifiedMeansMainActorIsolated && - ctx.LangOpts.DefaultIsolationBehavior == DefaultIsolation::MainActor) - if (auto result = - globalActorHelper(ctx.getMainActorType()->mapTypeOutOfContext())) - return *result; } // If we have an async function... by default we inherit isolation. diff --git a/stdlib/public/Concurrency/Actor.swift b/stdlib/public/Concurrency/Actor.swift index c72fa895a7622..81d5f2e6cd71f 100644 --- a/stdlib/public/Concurrency/Actor.swift +++ b/stdlib/public/Concurrency/Actor.swift @@ -140,3 +140,5 @@ public func extractIsolation( return Builtin.extractFunctionIsolation(fn) } #endif + +public typealias nonisolated = Never diff --git a/test/Concurrency/default_isolation.swift b/test/Concurrency/default_isolation.swift new file mode 100644 index 0000000000000..3b47162b2103b --- /dev/null +++ b/test/Concurrency/default_isolation.swift @@ -0,0 +1,44 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// REQUIRES: concurrency +// REQUIRES: swift_feature_DefaultIsolationTypealias + +// RUN: %target-swift-frontend -enable-experimental-feature DefaultIsolationTypealias -emit-sil -swift-version 6 -disable-availability-checking %t/main.swift %t/concurrent.swift | %FileCheck %s + +//--- main.swift + +private typealias DefaultIsolation = MainActor + +class C { + // CHECK: // static C.shared.getter + // CHECK-NEXT: // Isolation: global_actor. type: MainActor + static let shared = C() + + // CHECK: // C.init() + // CHECK-NEXT: // Isolation: global_actor. type: MainActor + init() {} +} + +// CHECK: // test() +// CHECK-NEXT: // Isolation: global_actor. type: MainActor +func test() { + // CHECK: // closure #1 in test() + // CHECK-NEXT: // Isolation: nonisolated + Task.detached { + let s = S(value: 0) + } +} + + +//--- concurrent.swift + +private typealias DefaultIsolation = nonisolated + +// CHECK: // S.init(value:) +// CHECK-NEXT: // Isolation: unspecified +struct S { + // CHECK: // S.value.getter + // CHECK-NEXT: // Isolation: unspecified + var value: Int +} diff --git a/test/Concurrency/default_isolation_invalid.swift b/test/Concurrency/default_isolation_invalid.swift new file mode 100644 index 0000000000000..2cbdaf78745a8 --- /dev/null +++ b/test/Concurrency/default_isolation_invalid.swift @@ -0,0 +1,26 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// REQUIRES: concurrency +// REQUIRES: swift_feature_DefaultIsolationTypealias + + +//--- invalid_access.swift + +// RUN: %target-swift-frontend -enable-experimental-feature DefaultIsolationTypealias -c -verify -swift-version 6 -disable-availability-checking %t/invalid_access.swift + +typealias DefaultIsolation = MainActor +// expected-error@-1 {{default isolation can only be set per file}} + + +//--- invalid_actor.swift + +// RUN: %target-swift-frontend -enable-experimental-feature DefaultIsolationTypealias -c -verify -swift-version 6 -disable-availability-checking %t/invalid_actor.swift + +@globalActor +actor CustomGlobalActor { + static let shared = CustomGlobalActor() +} + +private typealias DefaultIsolation = CustomGlobalActor +// expected-error@-1 {{default isolation can only be set to 'MainActor' or 'nonisolated'}}