Skip to content

Implement -strict-concurrency control and default to "minimal" #42523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ namespace swift {
Complete,
};

/// Describes how strict concurrency checking should be.
enum class StrictConcurrency {
/// Enforce Sendable constraints where it has been explicitly adopted and
/// perform actor-isolation checking wherever code has adopted concurrency.
Minimal,
/// Enforce Sendable constraints and perform actor-isolation checking
/// wherever code has adopted concurrency, including code that has
/// explicitly adopted Sendable.
Targeted,
/// Enforce Sendable constraints and actor-isolation checking throughout
/// the entire module.
Complete,
};

/// Access or distribution level of a library.
enum class LibraryLevel : uint8_t {
/// Application Programming Interface that is publicly distributed so
Expand Down Expand Up @@ -310,11 +324,8 @@ namespace swift {
/// optimized custom allocator, so that memory debugging tools can be used.
bool UseMalloc = false;

/// Provide additional warnings about code that is unsafe in the
/// eventual Swift concurrency model, and will eventually become errors
/// in a future Swift language version, but are too noisy for existing
/// language modes.
bool WarnConcurrency = false;
/// Specifies how strict concurrency checking will be.
StrictConcurrency StrictConcurrencyLevel = StrictConcurrency::Minimal;

/// Enable experimental #assert feature.
bool EnableExperimentalStaticAssert = false;
Expand Down
10 changes: 9 additions & 1 deletion include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,18 @@ def warn_swift3_objc_inference : Flag<["-"], "warn-swift3-objc-inference">,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild, HelpHidden]>;

def warn_concurrency : Flag<["-"], "warn-concurrency">,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild, ModuleInterfaceOptionIgnorable]>,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
HelpText<"Warn about code that is unsafe according to the Swift Concurrency "
"model and will become ill-formed in a future language version">;

def strict_concurrency : Joined<["-"], "strict-concurrency=">,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
HelpText<"Specify the how strict concurrency checking will be. The value may "
"be 'explicit' (most 'Sendable' checking is disabled), "
"'targeted' ('Sendable' checking is enabled in code that uses the "
"concurrency model, or 'complete' ('Sendable' and other checking is "
"enabled for all code in the module)">;

def Rpass_EQ : Joined<["-"], "Rpass=">,
Flags<[FrontendOption]>,
HelpText<"Report performed transformations by optimization passes whose "
Expand Down
4 changes: 3 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9162,7 +9162,9 @@ ActorIsolation swift::getActorIsolationOfContext(DeclContext *dc) {
}

if (auto *tld = dyn_cast<TopLevelCodeDecl>(dc)) {
if (dc->isAsyncContext() || dc->getASTContext().LangOpts.WarnConcurrency) {
if (dc->isAsyncContext() ||
dc->getASTContext().LangOpts.StrictConcurrencyLevel
>= StrictConcurrency::Complete) {
if (Type mainActor = dc->getASTContext().getMainActorType())
return ActorIsolation::forGlobalActor(
mainActor,
Expand Down
1 change: 1 addition & 0 deletions lib/Driver/ToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
options::OPT_enable_actor_data_race_checks,
options::OPT_disable_actor_data_race_checks);
inputArgs.AddLastArg(arguments, options::OPT_warn_concurrency);
inputArgs.AddLastArg(arguments, options::OPT_strict_concurrency);
inputArgs.AddLastArg(arguments, options::OPT_warn_implicit_overrides);
inputArgs.AddLastArg(arguments, options::OPT_typo_correction_limit);
inputArgs.AddLastArg(arguments, options::OPT_enable_app_extension);
Expand Down
23 changes: 22 additions & 1 deletion lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,28 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
}
}

Opts.WarnConcurrency |= Args.hasArg(OPT_warn_concurrency);
// Swift 6+ uses the strictest concurrency level.
if (Opts.isSwiftVersionAtLeast(6)) {
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
} else if (const Arg *A = Args.getLastArg(OPT_strict_concurrency)) {
auto value = llvm::StringSwitch<Optional<StrictConcurrency>>(A->getValue())
.Case("minimal", StrictConcurrency::Minimal)
.Case("targeted", StrictConcurrency::Targeted)
.Case("complete", StrictConcurrency::Complete)
.Default(None);

if (value)
Opts.StrictConcurrencyLevel = *value;
else
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());

} else if (Args.hasArg(OPT_warn_concurrency)) {
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
} else {
// Default to minimal checking in Swift 5.x.
Opts.StrictConcurrencyLevel = StrictConcurrency::Minimal;
}

Opts.WarnImplicitOverrides =
Args.hasArg(OPT_warn_implicit_overrides);
Expand Down
3 changes: 1 addition & 2 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1018,8 +1018,7 @@ ModuleDecl *CompilerInstance::getMainModule() const {
}
if (Invocation.getFrontendOptions().EnableLibraryEvolution)
MainModule->setResilienceStrategy(ResilienceStrategy::Resilient);
if (Invocation.getLangOptions().WarnConcurrency ||
Invocation.getLangOptions().isSwiftVersionAtLeast(6))
if (Invocation.getLangOptions().isSwiftVersionAtLeast(6))
MainModule->setIsConcurrencyChecked(true);

// Register the main module with the AST context.
Expand Down
5 changes: 2 additions & 3 deletions lib/IDE/CompletionLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,8 +750,7 @@ void CompletionLookup::analyzeActorIsolation(
// For "unsafe" global actor isolation, automatic 'async' only happens
// if the context has adopted concurrency.
if (!CanCurrDeclContextHandleAsync &&
!completionContextUsesConcurrencyFeatures(CurrDeclContext) &&
!CurrDeclContext->getParentModule()->isConcurrencyChecked()) {
!completionContextUsesConcurrencyFeatures(CurrDeclContext)) {
return;
}
LLVM_FALLTHROUGH;
Expand All @@ -769,7 +768,7 @@ void CompletionLookup::analyzeActorIsolation(
}

// If the reference is 'async', all types must be 'Sendable'.
if (CurrDeclContext->getParentModule()->isConcurrencyChecked() &&
if (Ctx.LangOpts.StrictConcurrencyLevel >= StrictConcurrency::Complete &&
implicitlyAsync && T) {
auto *M = CurrDeclContext->getParentModule();
if (isa<VarDecl>(VD)) {
Expand Down
60 changes: 48 additions & 12 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ GlobalActorAttributeRequest::evaluate(
// ... but not if it's an async-context top-level global
if (var->isTopLevelGlobal() &&
(var->getDeclContext()->isAsyncContext() ||
var->getASTContext().LangOpts.WarnConcurrency)) {
var->getASTContext().LangOpts.StrictConcurrencyLevel >=
StrictConcurrency::Complete)) {
var->diagnose(diag::global_actor_top_level_var)
.highlight(globalActorAttr->getRangeWithAt());
return None;
Expand Down Expand Up @@ -727,9 +728,6 @@ static bool hasUnavailableConformance(ProtocolConformanceRef conformance) {
}

static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
if (dc->getParentModule()->isConcurrencyChecked())
return true;

return contextRequiresStrictConcurrencyChecking(dc, [](const AbstractClosureExpr *) {
return Type();
});
Expand All @@ -738,7 +736,7 @@ static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
/// Determine the default diagnostic behavior for this language mode.
static DiagnosticBehavior defaultSendableDiagnosticBehavior(
const LangOptions &langOpts) {
// Prior to Swift 6, all Sendable-related diagnostics are warnings.
// Prior to Swift 6, all Sendable-related diagnostics are warnings at most.
if (!langOpts.isSwiftVersionAtLeast(6))
return DiagnosticBehavior::Warning;

Expand Down Expand Up @@ -770,6 +768,30 @@ DiagnosticBehavior SendableCheckContext::defaultDiagnosticBehavior() const {
return defaultSendableDiagnosticBehavior(fromDC->getASTContext().LangOpts);
}

DiagnosticBehavior
SendableCheckContext::implicitSendableDiagnosticBehavior() const {
switch (fromDC->getASTContext().LangOpts.StrictConcurrencyLevel) {
case StrictConcurrency::Targeted:
// Limited checking only diagnoses implicit Sendable within contexts that
// have adopted concurrency.
if (shouldDiagnoseExistingDataRaces(fromDC))
return DiagnosticBehavior::Warning;

LLVM_FALLTHROUGH;

case StrictConcurrency::Minimal:
// Explicit Sendable conformances always diagnose, even when strict
// strict checking is disabled.
if (isExplicitSendableConformance())
return DiagnosticBehavior::Warning;

return DiagnosticBehavior::Ignore;

case StrictConcurrency::Complete:
return defaultDiagnosticBehavior();
}
}

/// Determine whether the given nominal type has an explicit Sendable
/// conformance (regardless of its availability).
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
Expand Down Expand Up @@ -864,10 +886,10 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
: DiagnosticBehavior::Ignore;
}

auto defaultBehavior = defaultDiagnosticBehavior();
DiagnosticBehavior defaultBehavior = implicitSendableDiagnosticBehavior();

// If we are checking an implicit Sendable conformance, don't suppress
// diagnostics for declarations in the same module. We want them so make
// diagnostics for declarations in the same module. We want them to make
// enclosing inferred types non-Sendable.
if (defaultBehavior == DiagnosticBehavior::Ignore &&
nominal->getParentSourceFile() &&
Expand All @@ -885,7 +907,7 @@ bool swift::diagnoseSendabilityErrorBasedOn(
if (nominal) {
behavior = fromContext.diagnosticBehavior(nominal);
} else {
behavior = fromContext.defaultDiagnosticBehavior();
behavior = fromContext.implicitSendableDiagnosticBehavior();
}

bool wasSuppressed = diagnose(behavior);
Expand Down Expand Up @@ -2048,9 +2070,16 @@ namespace {
///
/// \returns true if we diagnosed the entity, \c false otherwise.
bool diagnoseReferenceToUnsafeGlobal(ValueDecl *value, SourceLoc loc) {
if (!getDeclContext()->getParentModule()->isConcurrencyChecked())
switch (value->getASTContext().LangOpts.StrictConcurrencyLevel) {
case StrictConcurrency::Minimal:
case StrictConcurrency::Targeted:
// Never diagnose.
return false;

case StrictConcurrency::Complete:
break;
}

// Only diagnose direct references to mutable global state.
auto var = dyn_cast<VarDecl>(value);
if (!var || var->isLet())
Expand Down Expand Up @@ -3954,7 +3983,8 @@ ActorIsolation ActorIsolationRequest::evaluate(

if (auto var = dyn_cast<VarDecl>(value)) {
if (var->isTopLevelGlobal() &&
(var->getASTContext().LangOpts.WarnConcurrency ||
(var->getASTContext().LangOpts.StrictConcurrencyLevel >=
StrictConcurrency::Complete ||
var->getDeclContext()->isAsyncContext())) {
if (Type mainActor = var->getASTContext().getMainActorType())
return inferredIsolation(
Expand Down Expand Up @@ -4155,10 +4185,16 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
bool swift::contextRequiresStrictConcurrencyChecking(
const DeclContext *dc,
llvm::function_ref<Type(const AbstractClosureExpr *)> getType) {
// If Swift >= 6, everything uses strict concurrency checking.
if (dc->getASTContext().LangOpts.isSwiftVersionAtLeast(6))
switch (dc->getASTContext().LangOpts.StrictConcurrencyLevel) {
case StrictConcurrency::Complete:
return true;

case StrictConcurrency::Targeted:
case StrictConcurrency::Minimal:
// Check below to see if the context has adopted concurrency features.
break;
}

while (!dc->isModuleScopeContext()) {
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
// A closure with an explicit global actor or nonindependent
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ struct SendableCheckContext {
/// Sendable conformance in this context.
DiagnosticBehavior defaultDiagnosticBehavior() const;

/// Determine the diagnostic behavior for an implicitly non-Sendable type.
DiagnosticBehavior implicitSendableDiagnosticBehavior() const;

/// Determine the diagnostic behavior when referencing the given nominal
/// type in this context.
DiagnosticBehavior diagnosticBehavior(NominalTypeDecl *nominal) const;
Expand Down
5 changes: 1 addition & 4 deletions test/ClangImporter/objc_async.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// 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
// 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=targeted -parse-as-library

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

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

sender.sendGeneric(sendableGeneric)
// expected-warning@-1 {{type 'GenericObject<SendableClass>' does not conform to the 'Sendable' protocol}}
// FIXME(rdar://89992569): Should allow for the possibility that GenericObject will have a Sendable subclass
sender.sendGeneric(nonSendableGeneric)
// expected-error@-1 {{argument type 'GenericObject<SendableClass>' does not conform to expected type 'Sendable'}}
// FIXME(rdar://89992095): Should be a warning because we're in -warn-concurrency
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ func testGlobalActorClosures() {
return 17
}

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

@available(SwiftStdlib 5.1, *)
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/async_tasks.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift -disable-availability-checking
// RUN: %target-typecheck-verify-swift -strict-concurrency=targeted -disable-availability-checking
// REQUIRES: concurrency

@available(SwiftStdlib 5.1, *)
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/concurrent_value_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func testConcurrency() {
func testUnsafeSendableNothing() {
var x = 5
acceptUnsafeSendable {
x = 17
x = 17 // expected-error{{mutation of captured var 'x' in concurrently-executing code}}
}
print(x)
}
Expand Down
4 changes: 3 additions & 1 deletion test/Concurrency/predates_concurrency_import.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -swift-version 6 %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking

// RUN: %target-typecheck-verify-swift -typecheck -I %t
// REQUIRES: concurrency
// REQUIRES: asserts

@preconcurrency import NonStrictModule
@_predatesConcurrency import StrictModule // expected-warning{{'@_predatesConcurrency' has been renamed to '@preconcurrency'}}
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/sendable_checking.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift
// RUN: %target-typecheck-verify-swift -strict-concurrency=targeted
// REQUIRES: concurrency
// REQUIRES: OS=macosx

Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/sendable_module_checking.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -I %t 2>&1 %s | %FileCheck %s
// RUN: %target-swift-frontend -typecheck -strict-concurrency=targeted -disable-availability-checking -I %t 2>&1 %s | %FileCheck %s

// REQUIRES: concurrency

Expand Down
5 changes: 3 additions & 2 deletions test/Concurrency/sendable_preconcurrency.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -swift-version 6 %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// RUN: %target-typecheck-verify-swift -disable-availability-checking -I %t
// RUN: %target-typecheck-verify-swift -strict-concurrency=targeted -disable-availability-checking -I %t

// REQUIRES: concurrency
// REQUIRES: asserts

import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
@preconcurrency import NonStrictModule
Expand Down
5 changes: 3 additions & 2 deletions test/Concurrency/sendable_without_preconcurrency.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -swift-version 6 %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// RUN: %target-typecheck-verify-swift -disable-availability-checking -I %t
// RUN: %target-typecheck-verify-swift -strict-concurrency=targeted -disable-availability-checking -I %t

// REQUIRES: concurrency
// REQUIRES: asserts

import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
import NonStrictModule
Expand Down
5 changes: 3 additions & 2 deletions test/Concurrency/sendable_without_preconcurrency_2.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -swift-version 6 %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// RUN: %target-typecheck-verify-swift -disable-availability-checking -I %t
// RUN: %target-typecheck-verify-swift -strict-concurrency=targeted -disable-availability-checking -I %t

// REQUIRES: concurrency
// REQUIRES: asserts

import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
import NonStrictModule // expected-remark{{add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'NonStrictModule'}}
Expand Down
Loading