Skip to content

Commit 12a11e5

Browse files
authored
Merge pull request swiftlang#42445 from DougGregor/strict-concurrency-flag
2 parents e1be532 + d200644 commit 12a11e5

File tree

13 files changed

+100
-47
lines changed

13 files changed

+100
-47
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ namespace swift {
5858
Complete,
5959
};
6060

61+
/// Describes how strict concurrency checking should be.
62+
enum class StrictConcurrency {
63+
/// Turns off strict checking, which disables (e.g.) Sendable checking in
64+
/// most cases.
65+
Off,
66+
/// Enables concurrency checking in a limited manner that is intended to
67+
/// only affect code that has already adopted the concurrency model.
68+
Limited,
69+
/// Enables strict concurrency checking throughout the entire model,
70+
/// providing an approximation of the fully-checked model.
71+
On
72+
};
73+
6174
/// Access or distribution level of a library.
6275
enum class LibraryLevel : uint8_t {
6376
/// Application Programming Interface that is publicly distributed so
@@ -310,11 +323,8 @@ namespace swift {
310323
/// optimized custom allocator, so that memory debugging tools can be used.
311324
bool UseMalloc = false;
312325

313-
/// Provide additional warnings about code that is unsafe in the
314-
/// eventual Swift concurrency model, and will eventually become errors
315-
/// in a future Swift language version, but are too noisy for existing
316-
/// language modes.
317-
bool WarnConcurrency = false;
326+
/// Specifies how strict concurrency checking will be.
327+
StrictConcurrency StrictConcurrencyLevel = StrictConcurrency::Limited;
318328

319329
/// Enable experimental #assert feature.
320330
bool EnableExperimentalStaticAssert = false;

include/swift/Option/Options.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,14 @@ def warn_concurrency : Flag<["-"], "warn-concurrency">,
699699
HelpText<"Warn about code that is unsafe according to the Swift Concurrency "
700700
"model and will become ill-formed in a future language version">;
701701

702+
def strict_concurrency : Joined<["-"], "strict-concurrency=">,
703+
Flags<[FrontendOption, ModuleInterfaceOptionIgnorable]>,
704+
HelpText<"Specify the how strict concurrency checking will be. The value may "
705+
"be 'off' (most 'Sendable' checking is disabled), "
706+
"'limited' ('Sendable' checking is enabled in code that uses the "
707+
"concurrency model, or 'on' ('Sendable' and other checking is "
708+
"enabled for all code in the module)">;
709+
702710
def Rpass_EQ : Joined<["-"], "Rpass=">,
703711
Flags<[FrontendOption]>,
704712
HelpText<"Report performed transformations by optimization passes whose "

lib/AST/Decl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9200,7 +9200,9 @@ ActorIsolation swift::getActorIsolationOfContext(DeclContext *dc) {
92009200
}
92019201

92029202
if (auto *tld = dyn_cast<TopLevelCodeDecl>(dc)) {
9203-
if (dc->isAsyncContext() || dc->getASTContext().LangOpts.WarnConcurrency) {
9203+
if (dc->isAsyncContext() ||
9204+
dc->getASTContext().LangOpts.StrictConcurrencyLevel
9205+
>= StrictConcurrency::On) {
92049206
if (Type mainActor = dc->getASTContext().getMainActorType())
92059207
return ActorIsolation::forGlobalActor(
92069208
mainActor,

lib/Driver/ToolChains.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
235235
options::OPT_enable_actor_data_race_checks,
236236
options::OPT_disable_actor_data_race_checks);
237237
inputArgs.AddLastArg(arguments, options::OPT_warn_concurrency);
238+
inputArgs.AddLastArg(arguments, options::OPT_strict_concurrency);
238239
inputArgs.AddLastArg(arguments, options::OPT_warn_implicit_overrides);
239240
inputArgs.AddLastArg(arguments, options::OPT_typo_correction_limit);
240241
inputArgs.AddLastArg(arguments, options::OPT_enable_app_extension);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,28 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
692692
}
693693
}
694694

695-
Opts.WarnConcurrency |= Args.hasArg(OPT_warn_concurrency);
695+
// Swift 6+ uses the strictest concurrency level.
696+
if (Opts.isSwiftVersionAtLeast(6)) {
697+
Opts.StrictConcurrencyLevel = StrictConcurrency::On;
698+
} else if (const Arg *A = Args.getLastArg(OPT_strict_concurrency)) {
699+
auto value = llvm::StringSwitch<Optional<StrictConcurrency>>(A->getValue())
700+
.Case("off", StrictConcurrency::Off)
701+
.Case("limited", StrictConcurrency::Limited)
702+
.Case("on", StrictConcurrency::On)
703+
.Default(None);
704+
705+
if (value)
706+
Opts.StrictConcurrencyLevel = *value;
707+
else
708+
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
709+
A->getAsString(Args), A->getValue());
710+
711+
} else if (Args.hasArg(OPT_warn_concurrency)) {
712+
Opts.StrictConcurrencyLevel = StrictConcurrency::On;
713+
} else {
714+
// Default to "limited" checking in Swift 5.x.
715+
Opts.StrictConcurrencyLevel = StrictConcurrency::Limited;
716+
}
696717

697718
Opts.WarnImplicitOverrides =
698719
Args.hasArg(OPT_warn_implicit_overrides);

lib/Frontend/Frontend.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,8 +1018,8 @@ ModuleDecl *CompilerInstance::getMainModule() const {
10181018
}
10191019
if (Invocation.getFrontendOptions().EnableLibraryEvolution)
10201020
MainModule->setResilienceStrategy(ResilienceStrategy::Resilient);
1021-
if (Invocation.getLangOptions().WarnConcurrency ||
1022-
Invocation.getLangOptions().isSwiftVersionAtLeast(6))
1021+
if (Invocation.getLangOptions().StrictConcurrencyLevel
1022+
>= StrictConcurrency::On)
10231023
MainModule->setIsConcurrencyChecked(true);
10241024

10251025
// Register the main module with the AST context.

lib/IDE/CompletionLookup.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,8 +750,7 @@ void CompletionLookup::analyzeActorIsolation(
750750
// For "unsafe" global actor isolation, automatic 'async' only happens
751751
// if the context has adopted concurrency.
752752
if (!CanCurrDeclContextHandleAsync &&
753-
!completionContextUsesConcurrencyFeatures(CurrDeclContext) &&
754-
!CurrDeclContext->getParentModule()->isConcurrencyChecked()) {
753+
!completionContextUsesConcurrencyFeatures(CurrDeclContext)) {
755754
return;
756755
}
757756
LLVM_FALLTHROUGH;
@@ -769,7 +768,7 @@ void CompletionLookup::analyzeActorIsolation(
769768
}
770769

771770
// If the reference is 'async', all types must be 'Sendable'.
772-
if (CurrDeclContext->getParentModule()->isConcurrencyChecked() &&
771+
if (Ctx.LangOpts.StrictConcurrencyLevel >= StrictConcurrency::On &&
773772
implicitlyAsync && T) {
774773
auto *M = CurrDeclContext->getParentModule();
775774
if (isa<VarDecl>(VD)) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ GlobalActorAttributeRequest::evaluate(
346346
// ... but not if it's an async-context top-level global
347347
if (var->isTopLevelGlobal() &&
348348
(var->getDeclContext()->isAsyncContext() ||
349-
var->getASTContext().LangOpts.WarnConcurrency)) {
349+
var->getASTContext().LangOpts.StrictConcurrencyLevel >=
350+
StrictConcurrency::On)) {
350351
var->diagnose(diag::global_actor_top_level_var)
351352
.highlight(globalActorAttr->getRangeWithAt());
352353
return None;
@@ -607,9 +608,6 @@ static bool hasUnavailableConformance(ProtocolConformanceRef conformance) {
607608
}
608609

609610
static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
610-
if (dc->getParentModule()->isConcurrencyChecked())
611-
return true;
612-
613611
return contextRequiresStrictConcurrencyChecking(dc, [](const AbstractClosureExpr *) {
614612
return Type();
615613
});
@@ -2117,9 +2115,16 @@ namespace {
21172115
///
21182116
/// \returns true if we diagnosed the entity, \c false otherwise.
21192117
bool diagnoseReferenceToUnsafeGlobal(ValueDecl *value, SourceLoc loc) {
2120-
if (!getDeclContext()->getParentModule()->isConcurrencyChecked())
2118+
switch (value->getASTContext().LangOpts.StrictConcurrencyLevel) {
2119+
case StrictConcurrency::Off:
2120+
case StrictConcurrency::Limited:
2121+
// Never diagnose.
21212122
return false;
21222123

2124+
case StrictConcurrency::On:
2125+
break;
2126+
}
2127+
21232128
// Only diagnose direct references to mutable global state.
21242129
auto var = dyn_cast<VarDecl>(value);
21252130
if (!var || var->isLet())
@@ -3683,7 +3688,8 @@ ActorIsolation ActorIsolationRequest::evaluate(
36833688

36843689
if (auto var = dyn_cast<VarDecl>(value)) {
36853690
if (var->isTopLevelGlobal() &&
3686-
(var->getASTContext().LangOpts.WarnConcurrency ||
3691+
(var->getASTContext().LangOpts.StrictConcurrencyLevel >=
3692+
StrictConcurrency::On ||
36873693
var->getDeclContext()->isAsyncContext())) {
36883694
if (Type mainActor = var->getASTContext().getMainActorType())
36893695
return inferredIsolation(
@@ -3891,10 +3897,16 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
38913897
bool swift::contextRequiresStrictConcurrencyChecking(
38923898
const DeclContext *dc,
38933899
llvm::function_ref<Type(const AbstractClosureExpr *)> getType) {
3894-
// If Swift >= 6, everything uses strict concurrency checking.
3895-
if (dc->getASTContext().LangOpts.isSwiftVersionAtLeast(6))
3900+
switch (dc->getASTContext().LangOpts.StrictConcurrencyLevel) {
3901+
case StrictConcurrency::On:
38963902
return true;
38973903

3904+
case StrictConcurrency::Limited:
3905+
case StrictConcurrency::Off:
3906+
// Check below to see if the context has adopted concurrency features.
3907+
break;
3908+
}
3909+
38983910
while (!dc->isModuleScopeContext()) {
38993911
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
39003912
// A closure with an explicit global actor or nonindependent

test/ClangImporter/objc_async.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules %s -verify -verify-additional-file %swift_src_root/test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h -warn-concurrency -parse-as-library
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules %s -verify -verify-additional-file %swift_src_root/test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h -strict-concurrency=limited -parse-as-library
22

33
// REQUIRES: objc_interop
44
// REQUIRES: concurrency
55
import Foundation
66
import ObjCConcurrency
7-
// expected-remark@-1{{add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'ObjCConcurrency'}}
87

98
@available(SwiftStdlib 5.5, *)
109
@MainActor func onlyOnMainActor() { }
@@ -358,8 +357,6 @@ func testSender(
358357
// expected-warning@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}
359358

360359
sender.sendGeneric(sendableGeneric)
361-
// expected-warning@-1 {{type 'GenericObject<SendableClass>' does not conform to the 'Sendable' protocol}}
362-
// FIXME(rdar://89992569): Should allow for the possibility that GenericObject will have a Sendable subclass
363360
sender.sendGeneric(nonSendableGeneric)
364361
// expected-error@-1 {{argument type 'GenericObject<SendableClass>' does not conform to expected type 'Sendable'}}
365362
// FIXME(rdar://89992095): Should be a warning because we're in -warn-concurrency

test/Concurrency/actor_isolation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ func testGlobalActorClosures() {
463463
return 17
464464
}
465465

466-
acceptConcurrentClosure { @SomeGlobalActor in 5 } // expected-warning{{converting function value of type '@SomeGlobalActor @Sendable () -> Int' to '@Sendable () -> Int' loses global actor 'SomeGlobalActor'}}
466+
acceptConcurrentClosure { @SomeGlobalActor in 5 } // expected-error{{converting function value of type '@SomeGlobalActor @Sendable () -> Int' to '@Sendable () -> Int' loses global actor 'SomeGlobalActor'}}
467467
}
468468

469469
@available(SwiftStdlib 5.1, *)

test/Concurrency/concurrent_value_checking.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func testConcurrency() {
174174
func testUnsafeSendableNothing() {
175175
var x = 5
176176
acceptUnsafeSendable {
177-
x = 17
177+
x = 17 // expected-error{{mutation of captured var 'x' in concurrently-executing code}}
178178
}
179179
print(x)
180180
}
Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -enable-experimental-async-top-level -swift-version 6 %s -verify
21
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -swift-version 6 %s -verify
32

43
// REQUIRES: asserts
@@ -8,45 +7,49 @@
87
// context. `a` is just a normal top-level global variable with no actor
98
// isolation.
109

11-
var a = 10 // expected-note 15 {{var declared here}}
10+
var a = 10 // expected-note 2 {{var declared here}}
11+
// expected-note@-1 2{{mutation of this var is only permitted within the actor}}
1212

13+
// expected-note@+1 3{{add '@MainActor' to make global function 'nonIsolatedSync()' part of global actor 'MainActor'}}
1314
func nonIsolatedSync() {
14-
print(a) // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
15-
a = a + 10 // expected-warning 2 {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
15+
print(a) // expected-error {{main actor-isolated var 'a' can not be referenced from a non-isolated context}}
16+
a = a + 10 // expected-error{{main actor-isolated var 'a' can not be referenced from a non-isolated context}}
17+
// expected-error@-1{{main actor-isolated var 'a' can not be mutated from a non-isolated context}}
1618
}
1719

1820
@MainActor
19-
func isolatedSync() { // expected-note 2 {{calls to global function 'isolatedSync()' from outside of its actor context are implicitly asynchronous}}
20-
print(a) // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
21-
a = a + 10 // expected-warning 2 {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
21+
func isolatedSync() {
22+
print(a)
23+
a = a + 10
2224
}
2325

2426
func nonIsolatedAsync() async {
25-
await print(a) // expected-warning {{no 'async' operations occur within 'await' expression}}
26-
// expected-warning@-1 {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
27-
a = a + 10 // expected-warning 2 {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
27+
await print(a)
28+
a = a + 10 // expected-error{{main actor-isolated var 'a' can not be mutated from a non-isolated context}}
29+
// expected-note@-1{{property access is 'async'}}
30+
// expected-error@-2{{expression is 'async' but is not marked with 'await'}}
2831
}
2932

3033
@MainActor
31-
func isolatedAsync() async { // expected-note 2 {{calls to global function 'isolatedAsync()' from outside of its actor context are implicitly asynchronous}}
32-
print(a) // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
33-
a = a + 10 // expected-warning 2 {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
34+
func isolatedAsync() async {
35+
print(a)
36+
a = a + 10
3437
}
3538

3639
nonIsolatedSync()
37-
isolatedSync() // expected-error {{call to main actor-isolated global function 'isolatedSync()' in a synchronous nonisolated context}}
40+
isolatedSync()
3841
nonIsolatedAsync() // expected-error {{'async' call in a function that does not support concurrency}}
39-
isolatedAsync() // expected-error {{call to main actor-isolated global function 'isolatedAsync()' in a synchronous nonisolated context}}
42+
isolatedAsync()
4043
// expected-error@-1 {{'async' call in a function that does not support concurrency}}
4144

42-
print(a) // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
45+
print(a)
4346

44-
if a > 10 { // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
47+
if a > 10 {
4548
nonIsolatedSync()
46-
isolatedSync() // expected-error {{call to main actor-isolated global function 'isolatedSync()' in a synchronous nonisolated context}}
49+
isolatedSync()
4750
nonIsolatedAsync() // expected-error {{'async' call in a function that does not support concurrency}}
48-
isolatedAsync() // expected-error {{call to main actor-isolated global function 'isolatedAsync()' in a synchronous nonisolated context}}
51+
isolatedAsync()
4952
// expected-error@-1 {{'async' call in a function that does not support concurrency}}
5053

51-
print(a) // expected-warning {{reference to var 'a' is not concurrency-safe because it involves shared mutable state}}
54+
print(a)
5255
}

tools/swift-ide-test/swift-ide-test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4281,7 +4281,7 @@ int main(int argc, char *argv[]) {
42814281
InitInvok.getLangOptions().EnableExperimentalConcurrency = true;
42824282
}
42834283
if (options::WarnConcurrency) {
4284-
InitInvok.getLangOptions().WarnConcurrency = true;
4284+
InitInvok.getLangOptions().StrictConcurrencyLevel = StrictConcurrency::On;
42854285
}
42864286
if (options::DisableImplicitConcurrencyImport) {
42874287
InitInvok.getLangOptions().DisableImplicitConcurrencyModuleImport = true;

0 commit comments

Comments
 (0)