Skip to content

Commit 762d518

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 3271c19 commit 762d518

22 files changed

+366
-23
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
@@ -8363,6 +8363,15 @@ ERROR(attr_execution_type_attr_incompatible_with_isolated_any,none,
83638363
"cannot use '@execution' together with @isolated(any)",
83648364
())
83658365

8366+
//===----------------------------------------------------------------------===//
8367+
// MARK: SwiftSettings
8368+
//===----------------------------------------------------------------------===//
8369+
8370+
ERROR(swift_settings_invalid_setting, none,
8371+
"Unrecognized setting passed to #SwiftSettings", ())
8372+
8373+
ERROR(swift_settings_must_be_top_level, none,
8374+
"#SwiftSettings can only be invoked as a top level declaration", ())
83668375

83678376
#define UNDEFINE_DIAGNOSTIC_MACROS
83688377
#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/KnownStdlibTypes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,6 @@ KNOWN_STDLIB_TYPE_DECL(Result, NominalTypeDecl, 2)
101101

102102
KNOWN_STDLIB_TYPE_DECL(InlineArray, NominalTypeDecl, 2)
103103

104+
KNOWN_STDLIB_TYPE_DECL(SwiftSetting, NominalTypeDecl, 0)
105+
104106
#undef KNOWN_STDLIB_TYPE_DECL

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
/// Syntax sugar features for concurrency.
477480
EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true)
478481

lib/AST/ASTPrinter.cpp

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

lib/AST/FeatureSet.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ using namespace swift;
2626

2727
/// Does the interface of this declaration use a type for which the
2828
/// given predicate returns true?
29-
static bool usesTypeMatching(Decl *decl, llvm::function_ref<bool(Type)> fn) {
29+
static bool usesTypeMatching(const Decl *decl,
30+
llvm::function_ref<bool(Type)> fn) {
3031
if (auto value = dyn_cast<ValueDecl>(decl)) {
3132
if (Type type = value->getInterfaceType()) {
3233
return type.findIf(fn);
@@ -389,6 +390,24 @@ UNINTERESTING_FEATURE(AssumeResilientCxxTypes)
389390
UNINTERESTING_FEATURE(CoroutineAccessorsUnwindOnCallerError)
390391
UNINTERESTING_FEATURE(CoroutineAccessorsAllocateInCallee)
391392

393+
static bool usesFeatureSwiftSettings(const Decl *decl) {
394+
// If we cannot find the struct SwiftSetting decl, then we could not have
395+
// specified #SwiftSettings.
396+
if (!decl->getASTContext().getSwiftSettingDecl())
397+
return false;
398+
399+
if (auto *nom = dyn_cast<NominalTypeDecl>(decl))
400+
return nom == decl->getASTContext().getSwiftSettingDecl();
401+
if (auto *ext = dyn_cast<ExtensionDecl>(decl))
402+
if (auto *nominal = ext->getExtendedNominal())
403+
if (usesFeatureSwiftSettings(nominal))
404+
return true;
405+
return usesTypeMatching(decl, [&](Type type) -> bool {
406+
return type->getCanonicalType() ==
407+
decl->getASTContext().getSwiftSettingType()->getCanonicalType();
408+
});
409+
}
410+
392411
bool swift::usesFeatureIsolatedDeinit(const Decl *decl) {
393412
if (auto cd = dyn_cast<ClassDecl>(decl)) {
394413
return cd->getFormalAccess() == AccessLevel::Open &&

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
@@ -5599,28 +5599,59 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
55995599

56005600
// If we are supposed to infer main actor isolation by default for entities
56015601
// within our module, make our default isolation main actor.
5602-
if (ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated) &&
5603-
value->getModuleContext() == ctx.MainModule) {
5602+
if (value->getModuleContext() == ctx.MainModule) {
5603+
auto globalActorHelper = [&](Type globalActor)
5604+
-> std::optional<std::tuple<InferredActorIsolation, ValueDecl *,
5605+
std::optional<ActorIsolation>>> {
5606+
// Default global actor isolation does not apply to any declarations
5607+
// within actors and distributed actors.
5608+
bool inActorContext = false;
5609+
auto *dc = value->getInnermostDeclContext();
5610+
while (dc && !inActorContext) {
5611+
if (auto *nominal = dc->getSelfNominalTypeDecl()) {
5612+
inActorContext = nominal->isAnyActor();
5613+
}
5614+
dc = dc->getParent();
5615+
}
56045616

5605-
// Default global actor isolation does not apply to any declarations
5606-
// within actors and distributed actors.
5607-
bool inActorContext = false;
5608-
auto *dc = value->getInnermostDeclContext();
5609-
while (dc && !inActorContext) {
5610-
if (auto *nominal = dc->getSelfNominalTypeDecl()) {
5611-
inActorContext = nominal->isAnyActor();
5617+
if (!inActorContext) {
5618+
// FIXME: deinit should be implicitly MainActor too.
5619+
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
5620+
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
5621+
isa<ConstructorDecl>(value)) {
5622+
return {
5623+
{{ActorIsolation::forGlobalActor(globalActor), {}}, nullptr, {}}};
5624+
}
56125625
}
5613-
dc = dc->getParent();
5614-
}
56155626

5616-
if (!inActorContext) {
5617-
// FIXME: deinit should be implicitly MainActor too.
5618-
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
5619-
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
5620-
isa<ConstructorDecl>(value)) {
5621-
return {{ActorIsolation::forMainActor(ctx), {}}, nullptr, {}};
5627+
return {};
5628+
};
5629+
5630+
// Otherwise, see if we have one specified by our file unit.
5631+
bool ignoreUnspecifiedMeansMainActorIsolated = false;
5632+
if (ctx.LangOpts.hasFeature(Feature::SwiftSettings)) {
5633+
if (auto *sourceFile = value->getDeclContext()->getParentSourceFile()) {
5634+
auto options = sourceFile->getLanguageOptions();
5635+
if (auto isolation = options.defaultIsolation) {
5636+
if (*isolation) {
5637+
auto result = globalActorHelper(*options.defaultIsolation);
5638+
if (result)
5639+
return *result;
5640+
} else {
5641+
// If we found a nil type, then we know we should ignore unspecified
5642+
// means main actor isolated.
5643+
ignoreUnspecifiedMeansMainActorIsolated = true;
5644+
}
5645+
}
56225646
}
56235647
}
5648+
5649+
// If we are required to use main actor... just use that.
5650+
if (!ignoreUnspecifiedMeansMainActorIsolated &&
5651+
ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated))
5652+
if (auto result =
5653+
globalActorHelper(ctx.getMainActorType()->mapTypeOutOfContext()))
5654+
return *result;
56245655
}
56255656

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

0 commit comments

Comments
 (0)