Skip to content

Commit 8be2c83

Browse files
Merge pull request #70811 from sophiapoirier/globals-preconcurrency-warning
🍒 @preconcurrency import downgrades globals concurrency errors to warnings
2 parents d5a07e9 + fee67ca commit 8be2c83

File tree

9 files changed

+145
-87
lines changed

9 files changed

+145
-87
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,16 @@ namespace swift {
561561
/// emitted as a warning, but a note will still be emitted as a note.
562562
InFlightDiagnostic &limitBehavior(DiagnosticBehavior limit);
563563

564+
/// Conditionally prevent the diagnostic from behaving more severely than \p
565+
/// limit. If the condition is false, no limit is imposed.
566+
InFlightDiagnostic &limitBehaviorIf(bool shouldLimit,
567+
DiagnosticBehavior limit) {
568+
if (!shouldLimit) {
569+
return *this;
570+
}
571+
return limitBehavior(limit);
572+
}
573+
564574
/// Limit the diagnostic behavior to warning until the specified version.
565575
///
566576
/// This helps stage in fixes for stricter diagnostics as warnings

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5329,18 +5329,18 @@ ERROR(distributed_actor_remote_func_must_not_be_distributed,none,
53295329
NOTE(actor_mutable_state,none,
53305330
"mutation of this %0 is only permitted within the actor",
53315331
(DescriptiveDeclKind))
5332-
WARNING(shared_mutable_state_access,none,
5333-
"reference to %kind0 is not concurrency-safe because it involves "
5334-
"shared mutable state", (const ValueDecl *))
5335-
WARNING(shared_mutable_state_decl,none,
5336-
"%kind0 is not concurrency-safe because it is non-isolated global "
5337-
"shared mutable state", (const ValueDecl *))
5332+
ERROR(shared_mutable_state_access,none,
5333+
"reference to %kind0 is not concurrency-safe because it involves shared "
5334+
"mutable state", (const ValueDecl *))
5335+
ERROR(shared_mutable_state_decl,none,
5336+
"%kind0 is not concurrency-safe because it is non-isolated global shared "
5337+
"mutable state", (const ValueDecl *))
53385338
NOTE(shared_mutable_state_decl_note,none,
53395339
"isolate %0 to a global actor, or convert it to a 'let' constant and "
53405340
"conform it to 'Sendable'", (const ValueDecl *))
5341-
WARNING(shared_immutable_state_decl,none,
5342-
"%kind0 is not concurrency-safe because it is not either conforming to "
5343-
"'Sendable' or isolated to a global actor", (const ValueDecl *))
5341+
ERROR(shared_immutable_state_decl,none,
5342+
"%kind0 is not concurrency-safe because it is not either conforming to "
5343+
"'Sendable' or isolated to a global actor", (const ValueDecl *))
53445344
ERROR(actor_isolated_witness,none,
53455345
"%select{|distributed }0%1 %kind2 cannot be used to satisfy %3 protocol "
53465346
"requirement",

include/swift/Basic/Features.def

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ UPCOMING_FEATURE(BareSlashRegexLiterals, 354, 6)
118118
UPCOMING_FEATURE(DeprecateApplicationMain, 383, 6)
119119
UPCOMING_FEATURE(ImportObjcForwardDeclarations, 384, 6)
120120
UPCOMING_FEATURE(DisableOutwardActorInference, 401, 6)
121+
UPCOMING_FEATURE(GlobalConcurrency, 412, 6)
121122

122123
UPCOMING_FEATURE(ExistentialAny, 335, 7)
123124

@@ -221,9 +222,6 @@ EXPERIMENTAL_FEATURE(StrictConcurrency, true)
221222
/// Defer Sendable checking to SIL diagnostic phase.
222223
EXPERIMENTAL_FEATURE(SendNonSendable, false)
223224

224-
/// Within strict concurrency, narrow global variable isolation requirements.
225-
EXPERIMENTAL_FEATURE(GlobalConcurrency, false)
226-
227225
/// Allow default values to require isolation at the call-site.
228226
EXPERIMENTAL_FEATURE(IsolatedDefaultValues, false)
229227

lib/Frontend/CompilerInvocation.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,11 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
976976
Opts.StrictConcurrencyLevel = StrictConcurrency::Minimal;
977977
}
978978

979+
// StrictConcurrency enables all data-race safety upcoming features.
980+
if (Opts.hasFeature(Feature::StrictConcurrency)) {
981+
Opts.Features.insert(Feature::GlobalConcurrency);
982+
}
983+
979984
Opts.WarnImplicitOverrides =
980985
Args.hasArg(OPT_warn_implicit_overrides);
981986

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -821,12 +821,12 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
821821
conformance.getConcrete())->isMissing());
822822
}
823823

824-
/// Find the import that makes the given nominal declaration available.
824+
/// Find the import that makes the given declaration available.
825825
static llvm::Optional<AttributedImport<ImportedModule>>
826-
findImportFor(NominalTypeDecl *nominal, const DeclContext *fromDC) {
827-
// If the nominal type is from the current module, there's no import.
828-
auto nominalModule = nominal->getParentModule();
829-
if (nominalModule == fromDC->getParentModule())
826+
findImportFor(const DeclContext *dc, const DeclContext *fromDC) {
827+
// If the type is from the current module, there's no import.
828+
auto module = dc->getParentModule();
829+
if (module == fromDC->getParentModule())
830830
return llvm::None;
831831

832832
auto fromSourceFile = fromDC->getParentSourceFile();
@@ -835,16 +835,16 @@ findImportFor(NominalTypeDecl *nominal, const DeclContext *fromDC) {
835835

836836
// Look to see if the owning module was directly imported.
837837
for (const auto &import : fromSourceFile->getImports()) {
838-
if (import.module.importedModule == nominalModule)
838+
if (import.module.importedModule == module)
839839
return import;
840840
}
841841

842842
// Now look for transitive imports.
843-
auto &importCache = nominal->getASTContext().getImportCache();
843+
auto &importCache = dc->getASTContext().getImportCache();
844844
for (const auto &import : fromSourceFile->getImports()) {
845845
auto &importSet = importCache.getImportSet(import.module.importedModule);
846846
for (const auto &transitive : importSet.getTransitiveImports()) {
847-
if (transitive.importedModule == nominalModule) {
847+
if (transitive.importedModule == module) {
848848
return import;
849849
}
850850
}
@@ -2800,7 +2800,8 @@ namespace {
28002800
///
28012801
/// \returns true if we diagnosed the entity, \c false otherwise.
28022802
bool diagnoseReferenceToUnsafeGlobal(ValueDecl *value, SourceLoc loc) {
2803-
switch (value->getASTContext().LangOpts.StrictConcurrencyLevel) {
2803+
auto &ctx = value->getASTContext();
2804+
switch (ctx.LangOpts.StrictConcurrencyLevel) {
28042805
case StrictConcurrency::Minimal:
28052806
case StrictConcurrency::Targeted:
28062807
// Never diagnose.
@@ -2823,16 +2824,32 @@ namespace {
28232824
return false;
28242825

28252826
// If it's actor-isolated, it's already been dealt with.
2826-
if (getActorIsolation(value).isActorIsolated())
2827+
const auto isolation = getActorIsolation(value);
2828+
if (isolation.isActorIsolated())
28272829
return false;
28282830

28292831
if (auto attr = value->getAttrs().getAttribute<NonisolatedAttr>();
28302832
attr && attr->isUnsafe()) {
28312833
return false;
28322834
}
28332835

2834-
ctx.Diags.diagnose(loc, diag::shared_mutable_state_access, value);
2836+
const auto import =
2837+
findImportFor(var->getDeclContext(), getDeclContext());
2838+
const bool isPreconcurrencyImport =
2839+
import && import->options.contains(ImportFlags::Preconcurrency);
2840+
const auto isPreconcurrencyUnspecifiedIsolation =
2841+
isPreconcurrencyImport && isolation.isUnspecified();
2842+
ctx.Diags.diagnose(loc, diag::shared_mutable_state_access, value)
2843+
.warnUntilSwiftVersionIf(!isPreconcurrencyUnspecifiedIsolation, 6)
2844+
.limitBehaviorIf(isPreconcurrencyImport, DiagnosticBehavior::Warning)
2845+
.limitBehaviorIf(!ctx.isSwiftVersionAtLeast(6) &&
2846+
isPreconcurrencyUnspecifiedIsolation,
2847+
DiagnosticBehavior::Ignore);
28352848
value->diagnose(diag::kind_declared_here, value->getDescriptiveKind());
2849+
if (const auto sourceFile = getDeclContext()->getParentSourceFile();
2850+
sourceFile && isPreconcurrencyImport) {
2851+
sourceFile->setImportUsedPreconcurrency(*import);
2852+
}
28362853
return true;
28372854
}
28382855

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public struct Globals {
2+
public static let integerConstant = 0
3+
public static var integerMutable = 0
4+
5+
public static nonisolated(unsafe) let nonisolatedUnsafeIntegerConstant = 0
6+
public static nonisolated(unsafe) var nonisolatedUnsafeIntegerMutable = 0
7+
8+
@MainActor public static var actorInteger = 0
9+
10+
public init() {}
11+
}

test/Concurrency/experimental_feature_strictconcurrency.swift

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify %s
2-
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency=complete -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify %s
1+
// RUN: %target-swift-frontend -disable-availability-checking -enable-experimental-feature StrictConcurrency -emit-sil -o /dev/null -verify %s
2+
// RUN: %target-swift-frontend -disable-availability-checking -enable-experimental-feature StrictConcurrency=complete -emit-sil -o /dev/null -verify %s
33

44
// REQUIRES: concurrency
55
// REQUIRES: asserts
@@ -23,67 +23,6 @@ struct Test2: TestProtocol { // expected-warning{{conformance of 'C2' to 'Sendab
2323
typealias Value = C2
2424
}
2525

26-
@globalActor
27-
actor TestGlobalActor {
28-
static var shared = TestGlobalActor()
29-
}
30-
31-
@TestGlobalActor
32-
var mutableIsolatedGlobal = 1
33-
34-
var mutableNonisolatedGlobal = 1 // expected-warning{{var 'mutableNonisolatedGlobal' is not concurrency-safe because it is non-isolated global shared mutable state}}
35-
// expected-note@-1{{isolate 'mutableNonisolatedGlobal' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
36-
37-
let immutableGlobal = 1
38-
39-
final class TestSendable: Sendable {
40-
init() {}
41-
}
42-
43-
final class TestNonsendable {
44-
init() {}
45-
}
46-
47-
nonisolated(unsafe) let immutableNonisolatedUnsafeTopLevelGlobal = TestNonsendable()
48-
49-
@propertyWrapper
50-
public struct TestWrapper {
51-
public init() {}
52-
public var wrappedValue: Int {
53-
return 0
54-
}
55-
}
56-
57-
struct TestStatics {
58-
static let immutableExplicitSendable = TestSendable()
59-
static let immutableNonsendable = TestNonsendable() // expected-warning{{static property 'immutableNonsendable' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor}}
60-
static nonisolated(unsafe) let immutableNonisolatedUnsafe = TestNonsendable()
61-
static nonisolated let immutableNonisolated = TestNonsendable() // expected-warning{{static property 'immutableNonisolated' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor}}
62-
static let immutableInferredSendable = 0
63-
static var mutable = 0 // expected-warning{{static property 'mutable' is not concurrency-safe because it is non-isolated global shared mutable state}}
64-
// expected-note@-1{{isolate 'mutable' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
65-
// expected-note@-2{{static property declared here}}
66-
static var computedProperty: Int { 0 } // computed property that, though static, has no storage so is not a global
67-
@TestWrapper static var wrapped: Int // expected-warning{{static property 'wrapped' is not concurrency-safe because it is non-isolated global shared mutable state}}
68-
// expected-note@-1{{isolate 'wrapped' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
69-
}
70-
71-
@TestGlobalActor
72-
func f() {
73-
print(TestStatics.immutableExplicitSendable)
74-
print(TestStatics.immutableInferredSendable)
75-
print(TestStatics.mutable) // expected-warning{{reference to static property 'mutable' is not concurrency-safe because it involves shared mutable state}}
76-
}
77-
78-
func testLocalNonisolatedUnsafe() async {
79-
nonisolated(unsafe) var value = 1
80-
let task = Task {
81-
value = 2
82-
return value
83-
}
84-
print(await task.value)
85-
}
86-
8726
@MainActor
8827
func iterate(stream: AsyncStream<Int>) async {
8928
nonisolated(unsafe) var it = stream.makeAsyncIterator()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/GlobalVariables.swiftmodule -module-name GlobalVariables -parse-as-library -strict-concurrency=minimal -swift-version 5 %S/Inputs/GlobalVariables.swift
3+
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -swift-version 6 -I %t %s %s -emit-sil -o /dev/null -verify %s
4+
5+
// REQUIRES: concurrency
6+
// REQUIRES: asserts
7+
8+
@preconcurrency import GlobalVariables
9+
10+
@globalActor
11+
actor TestGlobalActor {
12+
static var shared = TestGlobalActor()
13+
}
14+
15+
@TestGlobalActor
16+
var mutableIsolatedGlobal = 1
17+
18+
var mutableNonisolatedGlobal = 1 // expected-error{{var 'mutableNonisolatedGlobal' is not concurrency-safe because it is non-isolated global shared mutable state}}
19+
// expected-note@-1{{isolate 'mutableNonisolatedGlobal' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
20+
21+
let immutableGlobal = 1
22+
23+
final class TestSendable: Sendable {
24+
init() {}
25+
}
26+
27+
final class TestNonsendable {
28+
init() {}
29+
}
30+
31+
nonisolated(unsafe) let immutableNonisolatedUnsafeTopLevelGlobal = TestNonsendable()
32+
33+
@propertyWrapper
34+
public struct TestWrapper {
35+
public init() {}
36+
public var wrappedValue: Int {
37+
return 0
38+
}
39+
}
40+
41+
struct TestStatics {
42+
static let immutableExplicitSendable = TestSendable()
43+
static let immutableNonsendable = TestNonsendable() // expected-error{{static property 'immutableNonsendable' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor}}
44+
static nonisolated(unsafe) let immutableNonisolatedUnsafe = TestNonsendable()
45+
static nonisolated let immutableNonisolated = TestNonsendable() // expected-error{{static property 'immutableNonisolated' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor}}
46+
static let immutableInferredSendable = 0
47+
static var mutable = 0 // expected-error{{static property 'mutable' is not concurrency-safe because it is non-isolated global shared mutable state}}
48+
// expected-note@-1{{isolate 'mutable' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
49+
// expected-note@-2{{static property declared here}}
50+
static var computedProperty: Int { 0 } // computed property that, though static, has no storage so is not a global
51+
@TestWrapper static var wrapped: Int // expected-error{{static property 'wrapped' is not concurrency-safe because it is non-isolated global shared mutable state}}
52+
// expected-note@-1{{isolate 'wrapped' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
53+
}
54+
55+
@TestGlobalActor
56+
func f() {
57+
print(TestStatics.immutableExplicitSendable)
58+
print(TestStatics.immutableInferredSendable)
59+
print(TestStatics.mutable) // expected-error{{reference to static property 'mutable' is not concurrency-safe because it involves shared mutable state}}
60+
print(Globals.actorInteger) // expected-error{{main actor-isolated static property 'actorInteger' can not be referenced from global actor 'TestGlobalActor'}}
61+
}
62+
63+
func testLocalNonisolatedUnsafe() async {
64+
nonisolated(unsafe) var value = 1
65+
let task = Task {
66+
value = 2
67+
return value
68+
}
69+
print(await task.value)
70+
}
71+
72+
func testImportedGlobals() { // expected-note{{add '@MainActor' to make global function 'testImportedGlobals()' part of global actor 'MainActor'}}
73+
let _ = Globals.integerConstant
74+
let _ = Globals.integerMutable // expected-warning{{reference to static property 'integerMutable' is not concurrency-safe because it involves shared mutable state}}
75+
let _ = Globals.nonisolatedUnsafeIntegerConstant
76+
let _ = Globals.nonisolatedUnsafeIntegerMutable
77+
let _ = Globals.actorInteger // expected-error{{main actor-isolated static property 'actorInteger' can not be referenced from a non-isolated context}}
78+
}

test/expr/closure/closures_swift6.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class C_56501 {
8181
}
8282
}
8383

84-
public class TestImplicitSelfForWeakSelfCapture {
84+
public final class TestImplicitSelfForWeakSelfCapture: Sendable {
8585
static let staticOptional: TestImplicitSelfForWeakSelfCapture? = .init()
8686
func method() { }
8787

0 commit comments

Comments
 (0)