diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 6374dea921d46..cc52c80782dc6 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -210,6 +210,9 @@ EXPERIMENTAL_FEATURE(ReferenceBindings, false) /// Enable the explicit 'import Builtin' and allow Builtin usage. EXPERIMENTAL_FEATURE(BuiltinModule, true) +// Enable strict concurrency. +EXPERIMENTAL_FEATURE(StrictConcurrency, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index cb5c3e8bf4918..3b5a9678f28ba 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3173,6 +3173,10 @@ static bool usesFeatureExistentialAny(Decl *decl) { return false; } +static bool usesFeatureStrictConcurrency(Decl *decl) { + return false; +} + static bool usesFeatureImportObjcForwardDeclarations(Decl *decl) { ClangNode clangNode = decl->getClangNode(); if (!clangNode) diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 31c45c876d6d0..e78c741963e67 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -509,6 +509,15 @@ static void diagnoseCxxInteropCompatMode(Arg *verArg, ArgList &Args, diags.diagnose(SourceLoc(), diag::valid_cxx_interop_modes, versStr); } +static llvm::Optional +parseStrictConcurrency(StringRef value) { + return llvm::StringSwitch>(value) + .Case("minimal", StrictConcurrency::Minimal) + .Case("targeted", StrictConcurrency::Targeted) + .Case("complete", StrictConcurrency::Complete) + .Default(llvm::None); +} + static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, DiagnosticEngine &Diags, const FrontendOptions &FrontendOpts) { @@ -763,9 +772,33 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, addFutureFeatureIfNotImplied(Feature::BareSlashRegexLiterals); for (const Arg *A : Args.filtered(OPT_enable_experimental_feature)) { + // Allow StrictConcurrency to have a value that corresponds to the + // -strict-concurrency= settings. + StringRef value = A->getValue(); + if (value.startswith("StrictConcurrency")) { + auto decomposed = value.split("="); + if (decomposed.first == "StrictConcurrency") { + bool handled; + if (decomposed.second == "") { + Opts.StrictConcurrencyLevel = StrictConcurrency::Complete; + handled = true; + } else if (auto level = parseStrictConcurrency(decomposed.second)) { + Opts.StrictConcurrencyLevel = *level; + handled = true; + } else { + handled = false; + } + + if (handled) { + Opts.Features.insert(Feature::StrictConcurrency); + continue; + } + } + } + // If this is a known experimental feature, allow it in +Asserts // (non-release) builds for testing purposes. - if (auto feature = getExperimentalFeature(A->getValue())) { + if (auto feature = getExperimentalFeature(value)) { #ifdef NDEBUG if (!isFeatureAvailableInProduction(*feature)) { Diags.diagnose(SourceLoc(), @@ -928,6 +961,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, } else if (Args.hasArg(OPT_warn_concurrency)) { Opts.StrictConcurrencyLevel = StrictConcurrency::Complete; + } else if (Opts.hasFeature(Feature::StrictConcurrency)) { + // Already set above. } else { // Default to minimal checking in Swift 5.x. Opts.StrictConcurrencyLevel = StrictConcurrency::Minimal; diff --git a/test/Concurrency/experimental_feature_strictconcurrency.swift b/test/Concurrency/experimental_feature_strictconcurrency.swift new file mode 100644 index 0000000000000..9caff99212e80 --- /dev/null +++ b/test/Concurrency/experimental_feature_strictconcurrency.swift @@ -0,0 +1,22 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StrictConcurrency +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StrictConcurrency=complete +// REQUIRES: concurrency + +class C1 { } // expected-note{{class 'C1' does not conform to the 'Sendable' protocol}} +class C2 { } + +@available(*, unavailable) +extension C2: Sendable {} // expected-note{{conformance of 'C2' to 'Sendable' has been explicitly marked unavailable here}} + +protocol TestProtocol { + associatedtype Value: Sendable +} + +struct Test1: TestProtocol { // expected-warning{{type 'Test1.Value' (aka 'C1') does not conform to the 'Sendable' protocol}} + typealias Value = C1 +} + +struct Test2: TestProtocol { // expected-warning{{conformance of 'C2' to 'Sendable' is unavailable}} + // expected-note@-1{{in associated type 'Self.Value' (inferred as 'C2')}} + typealias Value = C2 +} diff --git a/test/Concurrency/experimental_feature_strictconcurrency_targeted.swift b/test/Concurrency/experimental_feature_strictconcurrency_targeted.swift new file mode 100644 index 0000000000000..5d213d66d2231 --- /dev/null +++ b/test/Concurrency/experimental_feature_strictconcurrency_targeted.swift @@ -0,0 +1,18 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature StrictConcurrency=targeted +// REQUIRES: concurrency + +class C { // expected-note{{class 'C' does not conform to the 'Sendable' protocol}} + var counter = 0 +} + +func acceptsSendable(_: T) { } + +func testNoConcurrency(c: C) { + acceptsSendable(c) +} + +@available(SwiftStdlib 5.1, *) +func testConcurrency(c: C) async { + acceptsSendable(c) // expected-warning{{type 'C' does not conform to the 'Sendable' protocol}} +} +