Skip to content

Commit 3ae339c

Browse files
committed
[concurrency] Add initial support for SwiftSettings to control defaultIsolation at the file level.
We introduce a new macro called #SwiftSettings that can be used in conjunction with a new stdlib type called SwiftSetting to control the default isolation at the file level. It overrides the current default isolation whether it is the current nonisolated state or main actor (when -enable-experimental-feature UnspecifiedMeansMainActorIsolated is set).
1 parent ed9eef2 commit 3ae339c

21 files changed

+348
-22
lines changed

include/swift/AST/ASTBridging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2882,6 +2882,8 @@ enum ENUM_EXTENSIBILITY_ATTR(open) BridgedMacroDefinitionKind : size_t {
28822882
BridgedBuiltinExternalMacro,
28832883
/// The builtin definition for the "isolation" macro.
28842884
BridgedBuiltinIsolationMacro,
2885+
/// The builtin definition for the "SwiftSetting" macro.
2886+
BridgedBuiltinSwiftSettingsMacro,
28852887
};
28862888

28872889
struct BridgedASTType {

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8344,6 +8344,15 @@ ERROR(attr_execution_type_attr_incompatible_with_isolated_any,none,
83448344
"cannot use '@execution' together with @isolated(any)",
83458345
())
83468346

8347+
//===----------------------------------------------------------------------===//
8348+
// MARK: SwiftSettings
8349+
//===----------------------------------------------------------------------===//
8350+
8351+
ERROR(swift_settings_invalid_setting, none,
8352+
"Unrecognized setting passed to #SwiftSettings", ())
8353+
8354+
ERROR(swift_settings_must_be_top_level, none,
8355+
"#SwiftSettings can only be invoked as a top level declaration", ())
83478356

83488357
#define UNDEFINE_DIAGNOSTIC_MACROS
83498358
#include "DefineDiagnosticMacros.h"

include/swift/AST/FileUnit.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ class FileUnit : public DeclContext, public ASTAllocated<FileUnit> {
3939
friend class DirectOperatorLookupRequest;
4040
friend class DirectPrecedenceGroupLookupRequest;
4141

42-
// The pointer is FileUnit insted of SynthesizedFileUnit to break circularity.
42+
/// The pointer is FileUnit insted of SynthesizedFileUnit to break
43+
/// circularity.
4344
llvm::PointerIntPair<FileUnit *, 3, FileUnitKind> SynthesizedFileAndKind;
4445

4546
protected:

include/swift/AST/MacroDefinition.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,14 @@ struct ExternalMacroReference {
6969
};
7070

7171
/// Describes the known kinds of builtin macros.
72-
enum class BuiltinMacroKind: uint8_t {
72+
enum class BuiltinMacroKind : uint8_t {
7373
/// #externalMacro, which references an external macro.
7474
ExternalMacro,
7575
/// #isolation, which produces the isolation of the current context
7676
IsolationMacro,
77+
/// #SwiftSettings, which allows for the user to set a compiler setting at
78+
/// the file level
79+
SwiftSettingsMacro,
7780
};
7881

7982
/// A single replacement

include/swift/AST/SourceFile.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ enum class RestrictedImportKind {
5353
/// Import that limits the access level of imported entities.
5454
using ImportAccessLevel = std::optional<AttributedImport<ImportedModule>>;
5555

56+
/// Language options only for use with a specific SourceFile.
57+
///
58+
/// Vended by SourceFile::getLanguageOptions().
59+
struct SourceFileLangOptions {
60+
/// If unset, no value was provided. If a Type, that type is the type of the
61+
/// isolation. If set to an empty type, nil was specified explicitly.
62+
std::optional<Type> defaultIsolation;
63+
};
64+
5665
/// A file containing Swift source code.
5766
///
5867
/// This is a .swift or .sil file (or a virtual file, such as the contents of
@@ -562,6 +571,9 @@ class SourceFile final : public FileUnit {
562571
ObjCSelector selector,
563572
SmallVectorImpl<AbstractFunctionDecl *> &results) const override;
564573

574+
/// File level language options.
575+
SourceFileLangOptions getLanguageOptions() const;
576+
565577
protected:
566578
virtual void
567579
lookupOperatorDirect(Identifier name, OperatorFixity fixity,

include/swift/AST/TypeCheckRequests.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5228,6 +5228,25 @@ class SemanticAvailableAttrRequest
52285228
void cacheResult(std::optional<SemanticAvailableAttr> value) const;
52295229
};
52305230

5231+
class SourceFileLangOptionsRequest
5232+
: public SimpleRequest<SourceFileLangOptionsRequest,
5233+
SourceFileLangOptions(SourceFile *),
5234+
RequestFlags::Cached> {
5235+
public:
5236+
using SimpleRequest::SimpleRequest;
5237+
5238+
private:
5239+
friend SimpleRequest;
5240+
5241+
SourceFileLangOptions evaluate(Evaluator &evaluator,
5242+
SourceFile *sourceFile) const;
5243+
5244+
public:
5245+
bool isCached() const { return true; }
5246+
std::optional<SourceFileLangOptions> getCachedResult() const;
5247+
void cacheResult(SourceFileLangOptions value) const;
5248+
};
5249+
52315250
#define SWIFT_TYPEID_ZONE TypeChecker
52325251
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"
52335252
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,3 +616,6 @@ SWIFT_REQUEST(TypeChecker, SemanticAvailableAttrRequest,
616616
std::optional<SemanticAvailableAttr>
617617
(const AvailableAttr *, const Decl *),
618618
SeparatelyCached, NoLocationInfo)
619+
620+
SWIFT_REQUEST(TypeChecker, SourceFileLangOptionsRequest,
621+
SourceFileLangOptions (SourceFile *), Cached, NoLocationInfo)

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ EXPERIMENTAL_FEATURE(ExtensibleEnums, true)
473473
/// Allow isolated conformances.
474474
EXPERIMENTAL_FEATURE(IsolatedConformances, true)
475475

476+
/// Allow SwiftSettings
477+
EXPERIMENTAL_FEATURE(SwiftSettings, false)
478+
476479
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
477480
#undef EXPERIMENTAL_FEATURE
478481
#undef UPCOMING_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4722,6 +4722,9 @@ void PrintAST::visitMacroDecl(MacroDecl *decl) {
47224722
case BuiltinMacroKind::IsolationMacro:
47234723
Printer << "IsolationMacro";
47244724
break;
4725+
case BuiltinMacroKind::SwiftSettingsMacro:
4726+
Printer << "SwiftSettingsMacro";
4727+
break;
47254728
}
47264729
break;
47274730

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ UNINTERESTING_FEATURE(SafeInteropWrappers)
384384
UNINTERESTING_FEATURE(AssumeResilientCxxTypes)
385385
UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError)
386386
UNINTERESTING_FEATURE(CoroutineAccessorsAllocateInCallee)
387+
UNINTERESTING_FEATURE(SwiftSettings)
387388

388389
bool swift::usesFeatureIsolatedDeinit(const Decl *decl) {
389390
if (auto cd = dyn_cast<ClassDecl>(decl)) {

lib/AST/Module.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4167,3 +4167,121 @@ ModuleDecl::getAvailabilityDomainForIdentifier(Identifier identifier) const {
41674167

41684168
return AvailabilityDomain::forCustom(iter->getSecond());
41694169
}
4170+
4171+
namespace {
4172+
4173+
enum class SwiftSettingKind {
4174+
Unknown = 0,
4175+
DefaultIsolation,
4176+
};
4177+
4178+
struct SwiftSettingsWalker : ASTWalker {
4179+
ASTContext &ctx;
4180+
SourceFileLangOptions result;
4181+
4182+
SwiftSettingsWalker(ASTContext &ctx) : ctx(ctx), result() {}
4183+
4184+
CanType patternMatchDefaultIsolationMainActor(CallExpr *callExpr);
4185+
4186+
PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
4187+
if (auto *macroExpr = dyn_cast<MacroExpansionExpr>(expr);
4188+
macroExpr && macroExpr->getMacroRef()) {
4189+
if (auto *macro =
4190+
dyn_cast<MacroDecl>(macroExpr->getMacroRef().getDecl())) {
4191+
auto macroDef = macro->getDefinition();
4192+
if (macroDef.kind == MacroDefinition::Kind::Builtin &&
4193+
macroDef.getBuiltinKind() == BuiltinMacroKind::SwiftSettingsMacro) {
4194+
// Ok, we found our SwiftSettingsMacro. Lets start pattern matching.
4195+
for (auto arg : *macroExpr->getArgs()) {
4196+
// If we did not find any macro, we emit an unknown macro error.
4197+
bool foundMacro = false;
4198+
4199+
if (auto *callExpr = dyn_cast<CallExpr>(arg.getExpr())) {
4200+
if (auto *directCallee = dyn_cast<DotSyntaxCallExpr>(
4201+
callExpr->getDirectCallee())) {
4202+
if (auto *funcDecl =
4203+
dyn_cast<FuncDecl>(directCallee->getCalledValue())) {
4204+
auto kind = llvm::StringSwitch<SwiftSettingKind>(
4205+
funcDecl->getBaseIdentifier().get())
4206+
.Case("defaultIsolation",
4207+
SwiftSettingKind::DefaultIsolation)
4208+
.Default(SwiftSettingKind::Unknown);
4209+
switch (kind) {
4210+
case SwiftSettingKind::Unknown:
4211+
// Emit error.
4212+
break;
4213+
case SwiftSettingKind::DefaultIsolation:
4214+
if (auto actor =
4215+
patternMatchDefaultIsolationMainActor(callExpr)) {
4216+
result.defaultIsolation = actor;
4217+
foundMacro = true;
4218+
}
4219+
4220+
if (isa<NilLiteralExpr>(callExpr->getArgs()->getExpr(0))) {
4221+
result.defaultIsolation = {Type()};
4222+
foundMacro = true;
4223+
}
4224+
}
4225+
}
4226+
}
4227+
}
4228+
4229+
// If we did not pattern match a macro, emit an error.
4230+
if (!foundMacro) {
4231+
ctx.Diags.diagnose(arg.getStartLoc(),
4232+
diag::swift_settings_invalid_setting);
4233+
}
4234+
}
4235+
}
4236+
}
4237+
}
4238+
return Action::Continue(expr);
4239+
}
4240+
};
4241+
4242+
} // namespace
4243+
4244+
CanType
4245+
SwiftSettingsWalker::patternMatchDefaultIsolationMainActor(CallExpr *callExpr) {
4246+
auto *firstArg =
4247+
dyn_cast<InjectIntoOptionalExpr>(callExpr->getArgs()->getExpr(0));
4248+
if (!firstArg)
4249+
return CanType();
4250+
4251+
auto *erasureExpr = dyn_cast<ErasureExpr>(firstArg->getSubExpr());
4252+
if (!erasureExpr)
4253+
return CanType();
4254+
4255+
auto *selfExpr = dyn_cast<DotSelfExpr>(erasureExpr->getSubExpr());
4256+
if (!selfExpr)
4257+
return CanType();
4258+
4259+
auto *typeExpr = dyn_cast<TypeExpr>(selfExpr->getSubExpr());
4260+
if (!typeExpr)
4261+
return CanType();
4262+
4263+
auto instanceType = typeExpr->getInstanceType()->getCanonicalType();
4264+
if (instanceType != ctx.getMainActorType()->getCanonicalType())
4265+
return CanType();
4266+
4267+
return instanceType;
4268+
}
4269+
4270+
SourceFileLangOptions
4271+
SourceFileLangOptionsRequest::evaluate(Evaluator &evaluator,
4272+
SourceFile *f) const {
4273+
SwiftSettingsWalker walker(f->getASTContext());
4274+
4275+
if (f->getASTContext().LangOpts.hasFeature(Feature::SwiftSettings)) {
4276+
for (auto *decl : f->getTopLevelDecls())
4277+
decl->walk(walker);
4278+
}
4279+
4280+
return walker.result;
4281+
}
4282+
4283+
SourceFileLangOptions SourceFile::getLanguageOptions() const {
4284+
auto &eval = getASTContext().evaluator;
4285+
auto *self = const_cast<SourceFile *>(this);
4286+
return evaluateOrDefault(eval, SourceFileLangOptionsRequest{self}, {});
4287+
}

lib/ASTGen/Sources/MacroEvaluation/Macros.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ func checkMacroDefinition(
244244
case "IsolationMacro":
245245
return Int(BridgedMacroDefinitionKind.builtinIsolationMacro.rawValue)
246246

247+
case "SwiftSettingsMacro":
248+
return Int(BridgedMacroDefinitionKind.builtinSwiftSettingsMacro.rawValue)
249+
247250
// These builtins don't exist, but are put into the standard library at
248251
// least for documentation purposes right now. Don't emit a warning for
249252
// them, but do fail operation.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5585,28 +5585,59 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
55855585

55865586
// If we are supposed to infer main actor isolation by default for entities
55875587
// within our module, make our default isolation main actor.
5588-
if (ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated) &&
5589-
value->getModuleContext() == ctx.MainModule) {
5588+
if (value->getModuleContext() == ctx.MainModule) {
5589+
auto globalActorHelper = [&](Type globalActor)
5590+
-> std::optional<std::tuple<InferredActorIsolation, ValueDecl *,
5591+
std::optional<ActorIsolation>>> {
5592+
// Default global actor isolation does not apply to any declarations
5593+
// within actors and distributed actors.
5594+
bool inActorContext = false;
5595+
auto *dc = value->getInnermostDeclContext();
5596+
while (dc && !inActorContext) {
5597+
if (auto *nominal = dc->getSelfNominalTypeDecl()) {
5598+
inActorContext = nominal->isAnyActor();
5599+
}
5600+
dc = dc->getParent();
5601+
}
55905602

5591-
// Default global actor isolation does not apply to any declarations
5592-
// within actors and distributed actors.
5593-
bool inActorContext = false;
5594-
auto *dc = value->getInnermostDeclContext();
5595-
while (dc && !inActorContext) {
5596-
if (auto *nominal = dc->getSelfNominalTypeDecl()) {
5597-
inActorContext = nominal->isAnyActor();
5603+
if (!inActorContext) {
5604+
// FIXME: deinit should be implicitly MainActor too.
5605+
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
5606+
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
5607+
isa<ConstructorDecl>(value)) {
5608+
return {
5609+
{{ActorIsolation::forGlobalActor(globalActor), {}}, nullptr, {}}};
5610+
}
55985611
}
5599-
dc = dc->getParent();
5600-
}
56015612

5602-
if (!inActorContext) {
5603-
// FIXME: deinit should be implicitly MainActor too.
5604-
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
5605-
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
5606-
isa<ConstructorDecl>(value)) {
5607-
return {{ActorIsolation::forMainActor(ctx), {}}, nullptr, {}};
5613+
return {};
5614+
};
5615+
5616+
// Otherwise, see if we have one specified by our file unit.
5617+
bool ignoreUnspecifiedMeansMainActorIsolated = false;
5618+
if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) {
5619+
if (auto *sourceFile = value->getDeclContext()->getParentSourceFile()) {
5620+
auto options = sourceFile->getLanguageOptions();
5621+
if (auto isolation = options.defaultIsolation) {
5622+
if (*isolation) {
5623+
auto result = globalActorHelper(*options.defaultIsolation);
5624+
if (result)
5625+
return *result;
5626+
} else {
5627+
// If we found a nil type, then we know we should ignore unspecified
5628+
// means main actor isolated.
5629+
ignoreUnspecifiedMeansMainActorIsolated = true;
5630+
}
5631+
}
56085632
}
56095633
}
5634+
5635+
// If we are required to use main actor... just use that.
5636+
if (!ignoreUnspecifiedMeansMainActorIsolated &&
5637+
ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated))
5638+
if (auto result =
5639+
globalActorHelper(ctx.getMainActorType()->mapTypeOutOfContext()))
5640+
return *result;
56105641
}
56115642

56125643
// If we have an async function... by default we inherit isolation.

0 commit comments

Comments
 (0)