Skip to content

Commit 734ce1a

Browse files
committed
[Concurrency] Implement an experimental feature for setting default
actor isolation per file using a typealias. This feature is gated behind the `DefaultIsolationTypealias` experimental feature flag.
1 parent 7e3921c commit 734ce1a

File tree

11 files changed

+182
-11
lines changed

11 files changed

+182
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6029,6 +6029,13 @@ ERROR(global_actor_not_usable_from_inline,none,
60296029
NOTE(move_global_actor_attr_to_storage_decl,none,
60306030
"move global actor attribute to %kind0", (const ValueDecl *))
60316031

6032+
ERROR(default_isolation_internal,none,
6033+
"default isolation can only be set per file",
6034+
())
6035+
ERROR(default_isolation_custom,none,
6036+
"default isolation can only be set to 'MainActor' or 'nonisolated'",
6037+
())
6038+
60326039
ERROR(actor_isolation_multiple_attr_2,none,
60336040
"%kind0 has multiple actor-isolation attributes (%1 and %2)",
60346041
(const Decl *, DeclAttribute, DeclAttribute))

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ IDENTIFIER(decodeIfPresent)
7474
IDENTIFIER(Decoder)
7575
IDENTIFIER(decoder)
7676
IDENTIFIER(DefaultDistributedActorSystem)
77+
IDENTIFIER(DefaultIsolation)
7778
IDENTIFIER_(Differentiation)
7879
IDENTIFIER_WITH_NAME(PatternMatchVar, "$match")
7980
IDENTIFIER(dynamicallyCall)

include/swift/AST/KnownSDKTypes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0)
4040
KNOWN_SDK_TYPE_DECL(Concurrency, CheckedContinuation, NominalTypeDecl, 2)
4141
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2)
4242
KNOWN_SDK_TYPE_DECL(Concurrency, MainActor, NominalTypeDecl, 0)
43+
KNOWN_SDK_TYPE_DECL(Concurrency, nonisolated, NominalTypeDecl, 0)
4344
KNOWN_SDK_TYPE_DECL(Concurrency, Job, StructDecl, 0) // legacy type; prefer ExecutorJob
4445
KNOWN_SDK_TYPE_DECL(Concurrency, ExecutorJob, StructDecl, 0)
4546
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedJob, StructDecl, 0)

include/swift/AST/TypeCheckRequests.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "swift/AST/SourceFile.h"
3434
#include "swift/AST/Type.h"
3535
#include "swift/AST/TypeResolutionStage.h"
36+
#include "swift/Basic/LangOptions.h"
3637
#include "swift/Basic/Statistic.h"
3738
#include "swift/Basic/TaggedUnion.h"
3839
#include "swift/Basic/TypeID.h"
@@ -1621,6 +1622,25 @@ class ActorIsolationRequest :
16211622
bool isCached() const { return true; }
16221623
};
16231624

1625+
/// Determine the default actor isolation for the given source file.
1626+
class DefaultActorIsolationRequest :
1627+
public SimpleRequest<DefaultActorIsolationRequest,
1628+
std::optional<DefaultIsolation>(SourceFile *),
1629+
RequestFlags::Cached> {
1630+
public:
1631+
using SimpleRequest::SimpleRequest;
1632+
1633+
private:
1634+
friend SimpleRequest;
1635+
1636+
std::optional<DefaultIsolation>
1637+
evaluate(Evaluator &evaluator, SourceFile *file) const;
1638+
1639+
public:
1640+
// Caching
1641+
bool isCached() const { return true; }
1642+
};
1643+
16241644
/// Determine whether the given function should have an isolated 'self'.
16251645
class HasIsolatedSelfRequest :
16261646
public SimpleRequest<HasIsolatedSelfRequest,

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ SWIFT_REQUEST(TypeChecker, GlobalActorAttributeRequest,
182182
SWIFT_REQUEST(TypeChecker, ActorIsolationRequest,
183183
ActorIsolationState(ValueDecl *),
184184
Cached, NoLocationInfo)
185+
SWIFT_REQUEST(TypeChecker, DefaultActorIsolationRequest,
186+
std::optional<DefaultIsolation>(SourceFile *),
187+
Cached, NoLocationInfo)
185188
SWIFT_REQUEST(TypeChecker, HasIsolatedSelfRequest,
186189
bool(ValueDecl *),
187190
Uncached, NoLocationInfo)

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,9 @@ EXPERIMENTAL_FEATURE(InferIsolatedConformances, true)
501501
/// Allow SwiftSettings
502502
EXPERIMENTAL_FEATURE(SwiftSettings, false)
503503

504+
/// Enable setting default isolation per file via typealias.
505+
EXPERIMENTAL_FEATURE(DefaultIsolationTypealias, true)
506+
504507
/// Syntax sugar features for concurrency.
505508
EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true)
506509

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ UNINTERESTING_FEATURE(ImportNonPublicCxxMembers)
405405
UNINTERESTING_FEATURE(CXXForeignReferenceTypeInitializers)
406406
UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError)
407407
UNINTERESTING_FEATURE(AllowRuntimeSymbolDeclarations)
408+
UNINTERESTING_FEATURE(DefaultIsolationTypealias)
408409

409410
static bool usesFeatureSwiftSettings(const Decl *decl) {
410411
// We just need to guard `#SwiftSettings`.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5732,6 +5732,73 @@ static void addAttributesForActorIsolation(ValueDecl *value,
57325732
}
57335733
}
57345734

5735+
std::optional<DefaultIsolation>
5736+
DefaultActorIsolationRequest::evaluate(Evaluator &evaluator,
5737+
SourceFile *sourceFile) const {
5738+
auto &ctx = sourceFile->getASTContext();
5739+
5740+
auto defaultModuleIsolaton = ctx.LangOpts.DefaultIsolationBehavior;
5741+
auto mainActorType = ctx.getMainActorType();
5742+
if (!mainActorType)
5743+
return defaultModuleIsolaton;
5744+
5745+
if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) {
5746+
auto options = sourceFile->getLanguageOptions();
5747+
if (auto isolation = options.defaultIsolation) {
5748+
return *isolation;
5749+
}
5750+
}
5751+
5752+
if (ctx.LangOpts.hasFeature(Feature::DefaultIsolationTypealias)) {
5753+
auto nonisolatedType = ctx.getnonisolatedType();
5754+
5755+
auto decls = sourceFile->getTopLevelDecls();
5756+
if (decls.empty())
5757+
return defaultModuleIsolaton;
5758+
5759+
auto locInFile = decls.front()->getStartLoc();
5760+
auto defaultIsolationResult = TypeChecker::lookupUnqualified(
5761+
sourceFile->getModuleScopeContext(),
5762+
DeclNameRef(ctx.Id_DefaultIsolation),
5763+
locInFile);
5764+
for (auto found : defaultIsolationResult) {
5765+
auto *decl = found.getValueDecl();
5766+
if (!decl)
5767+
continue;
5768+
5769+
auto *typealias = dyn_cast<TypeAliasDecl>(decl);
5770+
if (!typealias ||
5771+
!typealias->getDeclContext()->isModuleScopeContext())
5772+
continue;
5773+
5774+
// We have a top-level 'DefaultIsolation' typealias. We can assume
5775+
// it's the only one, because multiple top-level typealiases with
5776+
// the same name is a redeclaration error, and default isolation
5777+
// cannot be set by an imported library.
5778+
5779+
// The typealias can only be used to set default isolation per file.
5780+
if (typealias->getFormalAccess() >= AccessLevel::Internal) {
5781+
typealias->diagnose(diag::default_isolation_internal);
5782+
break;
5783+
}
5784+
5785+
auto type = typealias->getUnderlyingType();
5786+
if (type->isEqual(mainActorType))
5787+
return DefaultIsolation::MainActor;
5788+
5789+
if (type->isEqual(nonisolatedType))
5790+
return DefaultIsolation::Nonisolated;
5791+
5792+
// The underlying type of the typealias must either be 'MainActor'
5793+
// or 'nonisolated'.
5794+
typealias->diagnose(diag::default_isolation_custom);
5795+
break;
5796+
}
5797+
}
5798+
5799+
return defaultModuleIsolaton;
5800+
}
5801+
57355802
/// Determine the default isolation and isolation source for this declaration,
57365803
/// which may still be overridden by other inference rules.
57375804
static std::tuple<InferredActorIsolation, ValueDecl *,
@@ -5741,17 +5808,13 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
57415808

57425809
// Determine whether default isolation is set to MainActor, either for
57435810
// the entire module or in this specific file.
5744-
if (value->getModuleContext() == ctx.MainModule) {
5811+
auto mainActorType = ctx.getMainActorType();
5812+
if (mainActorType && value->getModuleContext() == ctx.MainModule) {
57455813
// See if we have one specified by our file unit.
5746-
auto defaultIsolation = ctx.LangOpts.DefaultIsolationBehavior;
5747-
if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) {
5748-
if (auto *sourceFile = value->getDeclContext()->getParentSourceFile()) {
5749-
auto options = sourceFile->getLanguageOptions();
5750-
if (auto isolation = options.defaultIsolation) {
5751-
defaultIsolation = *isolation;
5752-
}
5753-
}
5754-
}
5814+
auto *sourceFile = value->getDeclContext()->getParentSourceFile();
5815+
auto defaultIsolation = evaluateOrDefault(
5816+
ctx.evaluator, DefaultActorIsolationRequest{sourceFile},
5817+
ctx.LangOpts.DefaultIsolationBehavior);
57555818

57565819
if (defaultIsolation == DefaultIsolation::MainActor) {
57575820
// Default global actor isolation does not apply to any declarations
@@ -5765,7 +5828,6 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
57655828
dc = dc->getParent();
57665829
}
57675830

5768-
auto mainActorType = ctx.getMainActorType()->mapTypeOutOfContext();
57695831
if (!inActorContext) {
57705832
// FIXME: deinit should be implicitly MainActor too.
57715833
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||

stdlib/public/Concurrency/Actor.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,6 @@ public func extractIsolation<each Arg, Result>(
140140
return Builtin.extractFunctionIsolation(fn)
141141
}
142142
#endif
143+
144+
@available(SwiftStdlib 6.2, *)
145+
public struct nonisolated {}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// REQUIRES: concurrency
5+
// REQUIRES: swift_feature_DefaultIsolationTypealias
6+
7+
// RUN: %target-swift-frontend -enable-experimental-feature DefaultIsolationTypealias -emit-sil -swift-version 6 -disable-availability-checking %t/main.swift %t/concurrent.swift | %FileCheck %s
8+
9+
//--- main.swift
10+
11+
private typealias DefaultIsolation = MainActor
12+
13+
class C {
14+
// CHECK: // static C.shared.getter
15+
// CHECK-NEXT: // Isolation: global_actor. type: MainActor
16+
static let shared = C()
17+
18+
// CHECK: // C.init()
19+
// CHECK-NEXT: // Isolation: global_actor. type: MainActor
20+
init() {}
21+
}
22+
23+
// CHECK: // test()
24+
// CHECK-NEXT: // Isolation: global_actor. type: MainActor
25+
func test() {
26+
// CHECK: // closure #1 in test()
27+
// CHECK-NEXT: // Isolation: nonisolated
28+
Task.detached {
29+
let s = S(value: 0)
30+
}
31+
}
32+
33+
34+
//--- concurrent.swift
35+
36+
private typealias DefaultIsolation = nonisolated
37+
38+
// CHECK: // S.init(value:)
39+
// CHECK-NEXT: // Isolation: unspecified
40+
struct S {
41+
// CHECK: // S.value.getter
42+
// CHECK-NEXT: // Isolation: unspecified
43+
var value: Int
44+
}

0 commit comments

Comments
 (0)