Skip to content

Commit 6eb42df

Browse files
authored
Merge pull request #73739 from DougGregor/global-var-concurrency-fixits
Provide more Fix-It guidance for concurrency-unsafe global variables (SE-0412)
2 parents 6f969be + d4ce618 commit 6eb42df

13 files changed

+102
-52
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5610,16 +5610,17 @@ ERROR(shared_mutable_state_access,none,
56105610
ERROR(shared_mutable_state_decl,none,
56115611
"%kind0 is not concurrency-safe because it is non-isolated global shared "
56125612
"mutable state", (const ValueDecl *))
5613-
NOTE(shared_mutable_state_decl_note,none,
5614-
"isolate %0 to a global actor, or convert it to a 'let' constant and "
5615-
"conform it to 'Sendable'", (const ValueDecl *))
56165613
ERROR(shared_immutable_state_decl,none,
56175614
"%kind1 is not concurrency-safe because non-'Sendable' type %0 may have "
56185615
"shared mutable state",
56195616
(Type, const ValueDecl *))
5620-
NOTE(shared_immutable_state_decl_note,none,
5621-
"isolate %0 to a global actor, or conform %1 to 'Sendable'",
5622-
(const ValueDecl *, Type))
5617+
NOTE(shared_state_make_immutable,none,
5618+
"convert %0 to a 'let' constant to make the shared state immutable",
5619+
(const ValueDecl *))
5620+
NOTE(shared_state_main_actor_node,none,
5621+
"restrict %0 to the main actor if it will only be accessed from the main thread", (const ValueDecl *))
5622+
NOTE(shared_state_nonisolated_unsafe,none,
5623+
"unsafely mark %0 as concurrency-safe if all accesses are protected by an external synchronization mechanism", (const ValueDecl *))
56235624
ERROR(actor_isolated_witness,none,
56245625
"%select{|distributed }0%1 %kind2 cannot be used to satisfy %3 protocol "
56255626
"requirement",

lib/Sema/MiscDiagnostics.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3779,6 +3779,28 @@ class ReturnTypePlaceholderReplacer : public ASTWalker {
37793779

37803780
} // end anonymous namespace
37813781

3782+
SourceLoc swift::getFixItLocForVarToLet(VarDecl *var) {
3783+
// Try to find the location of the 'var' so we can produce a fixit. If
3784+
// this is a simple PatternBinding, use its location.
3785+
if (auto *PBD = var->getParentPatternBinding()) {
3786+
if (PBD->getSingleVar() == var)
3787+
return PBD->getLoc();
3788+
} else if (auto *pattern = var->getParentPattern()) {
3789+
BindingPattern *foundVP = nullptr;
3790+
pattern->forEachNode([&](Pattern *P) {
3791+
if (auto *VP = dyn_cast<BindingPattern>(P))
3792+
if (VP->getSingleVar() == var)
3793+
foundVP = VP;
3794+
});
3795+
3796+
if (foundVP && foundVP->getIntroducer() != VarDecl::Introducer::Let) {
3797+
return foundVP->getLoc();
3798+
}
3799+
}
3800+
3801+
return SourceLoc();
3802+
}
3803+
37823804
// After we have scanned the entire region, diagnose variables that could be
37833805
// declared with a narrower usage kind.
37843806
VarDeclUsageChecker::~VarDeclUsageChecker() {
@@ -3998,25 +4020,7 @@ VarDeclUsageChecker::~VarDeclUsageChecker() {
39984020
// Don't warn if we have something like "let (x,y) = ..." and 'y' was
39994021
// never mutated, but 'x' was.
40004022
!isVarDeclPartOfPBDThatHadSomeMutation(var)) {
4001-
SourceLoc FixItLoc;
4002-
4003-
// Try to find the location of the 'var' so we can produce a fixit. If
4004-
// this is a simple PatternBinding, use its location.
4005-
if (auto *PBD = var->getParentPatternBinding()) {
4006-
if (PBD->getSingleVar() == var)
4007-
FixItLoc = PBD->getLoc();
4008-
} else if (auto *pattern = var->getParentPattern()) {
4009-
BindingPattern *foundVP = nullptr;
4010-
pattern->forEachNode([&](Pattern *P) {
4011-
if (auto *VP = dyn_cast<BindingPattern>(P))
4012-
if (VP->getSingleVar() == var)
4013-
foundVP = VP;
4014-
});
4015-
4016-
if (foundVP && foundVP->getIntroducer() != VarDecl::Introducer::Let) {
4017-
FixItLoc = foundVP->getLoc();
4018-
}
4019-
}
4023+
SourceLoc FixItLoc = getFixItLocForVarToLet(var);
40204024

40214025
// If this is a parameter explicitly marked 'var', remove it.
40224026
if (FixItLoc.isInvalid()) {

lib/Sema/MiscDiagnostics.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ namespace swift {
7070
AccessLevel desiredAccess, bool isForSetter = false,
7171
bool shouldUseDefaultAccess = false);
7272

73+
/// Compute the location of the 'var' keyword for a 'var'-to-'let' Fix-It.
74+
SourceLoc getFixItLocForVarToLet(VarDecl *var);
75+
7376
/// Describes the context of a parameter, for use in diagnosing argument
7477
/// label problems.
7578
enum class ParameterContext : unsigned {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// This file implements type checking support for Swift's concurrency model.
1414
//
1515
//===----------------------------------------------------------------------===//
16+
#include "MiscDiagnostics.h"
1617
#include "TypeCheckConcurrency.h"
1718
#include "TypeCheckDistributed.h"
1819
#include "TypeCheckInvertible.h"
@@ -5030,23 +5031,41 @@ ActorIsolation ActorIsolationRequest::evaluate(
50305031
if (auto *originalVar = var->getOriginalWrappedProperty()) {
50315032
diagVar = originalVar;
50325033
}
5034+
5035+
bool diagnosed = false;
50335036
if (var->isLet()) {
50345037
auto type = var->getInterfaceType();
5035-
bool diagnosed = diagnoseIfAnyNonSendableTypes(
5038+
diagnosed = diagnoseIfAnyNonSendableTypes(
50365039
type, SendableCheckContext(var->getDeclContext()),
50375040
/*inDerivedConformance=*/Type(), /*typeLoc=*/SourceLoc(),
50385041
/*diagnoseLoc=*/var->getLoc(),
50395042
diag::shared_immutable_state_decl, diagVar);
5040-
5041-
// If we diagnosed this 'let' as non-Sendable, tack on a note
5042-
// to suggest a course of action.
5043-
if (diagnosed)
5044-
diagVar->diagnose(diag::shared_immutable_state_decl_note,
5045-
diagVar, type);
50465043
} else {
50475044
diagVar->diagnose(diag::shared_mutable_state_decl, diagVar)
50485045
.warnUntilSwiftVersion(6);
5049-
diagVar->diagnose(diag::shared_mutable_state_decl_note, diagVar);
5046+
diagnosed = true;
5047+
}
5048+
5049+
// If we diagnosed this global, tack on notes to suggest potential
5050+
// courses of action.
5051+
if (diagnosed) {
5052+
if (!var->isLet()) {
5053+
auto diag = diagVar->diagnose(diag::shared_state_make_immutable,
5054+
diagVar);
5055+
SourceLoc fixItLoc = getFixItLocForVarToLet(diagVar);
5056+
if (fixItLoc.isValid()) {
5057+
diag.fixItReplace(fixItLoc, "let");
5058+
}
5059+
}
5060+
5061+
diagVar->diagnose(diag::shared_state_main_actor_node,
5062+
diagVar)
5063+
.fixItInsert(diagVar->getAttributeInsertionLoc(false),
5064+
"@MainActor ");
5065+
diagVar->diagnose(diag::shared_state_nonisolated_unsafe,
5066+
diagVar)
5067+
.fixItInsert(diagVar->getAttributeInsertionLoc(true),
5068+
"nonisolated(unsafe) ");
50505069
}
50515070
}
50525071
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
struct Foo {
22
static let member = Bar() // expected-complete-warning {{static property 'member' is not concurrency-safe because non-'Sendable' type 'Bar' may have shared mutable state; this is an error in the Swift 6 language mode}}
3-
// expected-complete-note@-1 {{isolate 'member' to a global actor, or conform 'Bar' to 'Sendable'}}
3+
// expected-complete-note@-1 {{restrict 'member' to the main actor if it will only be accessed from the main thread}}
4+
// expected-complete-note@-2{{unsafely mark 'member' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
45
}

test/Concurrency/actor_isolation.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import OtherActors // expected-warning{{add '@preconcurrency' to suppress 'Senda
1212

1313
let immutableGlobal: String = "hello"
1414

15-
// expected-warning@+2 {{var 'mutableGlobal' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
16-
// expected-note@+1 {{isolate 'mutableGlobal' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
15+
// expected-warning@+4 {{var 'mutableGlobal' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
16+
// expected-note@+3 {{convert 'mutableGlobal' to a 'let' constant to make the shared state immutable}}
17+
// expected-note@+2 {{restrict 'mutableGlobal' to the main actor if it will only be accessed from the main thread}}
18+
// expected-note@+1 {{unsafely mark 'mutableGlobal' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
1719
var mutableGlobal: String = "can't touch this" // expected-note 5{{var declared here}}
1820

1921
@available(SwiftStdlib 5.1, *)

test/Concurrency/concurrency_warnings.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ class GlobalCounter { // expected-note{{class 'GlobalCounter' does not conform t
99
}
1010

1111
let rs = GlobalCounter() // expected-warning {{let 'rs' is not concurrency-safe because non-'Sendable' type 'GlobalCounter' may have shared mutable state; this is an error in the Swift 6 language mode}}
12-
// expected-note@-1 {{isolate 'rs' to a global actor, or conform 'GlobalCounter' to 'Sendable'}}
12+
// expected-note@-1 {{restrict 'rs' to the main actor if it will only be accessed from the main thread}}
13+
// expected-note@-2 {{unsafely mark 'rs' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
14+
1315

1416
var globalInt = 17 // expected-warning {{var 'globalInt' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
15-
// expected-note@-1 {{isolate 'globalInt' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
17+
// expected-note@-1 {{restrict 'globalInt' to the main actor if it will only be accessed from the main thread}}
1618
// expected-note@-2 2{{var declared here}}
17-
19+
// expected-note@-3 {{unsafely mark 'globalInt' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
20+
// expected-note@-4 {{convert 'globalInt' to a 'let' constant to make the shared state immutable}}
1821

1922
class MyError: Error { // expected-warning{{non-final class 'MyError' cannot conform to 'Sendable'; use '@unchecked Sendable'}}
2023
var storage = 0 // expected-warning{{stored property 'storage' of 'Sendable'-conforming class 'MyError' is mutable}}

test/Concurrency/concurrent_value_checking.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ typealias BadGenericCF<T> = @Sendable () -> T?
274274
typealias GoodGenericCF<T: Sendable> = @Sendable () -> T? // okay
275275

276276
var concurrentFuncVar: (@Sendable (NotConcurrent) -> Void)? = nil // expected-warning{{var 'concurrentFuncVar' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
277-
// expected-note@-1 {{isolate 'concurrentFuncVar' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
277+
// expected-note@-1 {{restrict 'concurrentFuncVar' to the main actor if it will only be accessed from the main thread}}
278+
// expected-note@-2 {{unsafely mark 'concurrentFuncVar' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
279+
// expected-note@-3 {{convert 'concurrentFuncVar' to a 'let' constant to make the shared state immutable}}
278280

279281
// ----------------------------------------------------------------------
280282
// Sendable restriction on @Sendable closures.

test/Concurrency/flow_isolation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,8 +520,10 @@ struct CardboardBox<T> {
520520

521521
@available(SwiftStdlib 5.1, *)
522522
var globalVar: EscapeArtist? // expected-warning {{var 'globalVar' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
523-
// expected-note@-1 {{isolate 'globalVar' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
523+
// expected-note@-1 {{restrict 'globalVar' to the main actor if it will only be accessed from the main thread}}
524524
// expected-note@-2 2 {{var declared here}}
525+
// expected-note@-3 {{unsafely mark 'globalVar' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
526+
// expected-note@-4 {{convert 'globalVar' to a 'let' constant to make the shared state immutable}}
525527

526528
@available(SwiftStdlib 5.1, *)
527529
actor EscapeArtist {

test/Concurrency/freestanding_top_level.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// RUN: %target-swift-frontend -concurrency-model=task-to-thread -typecheck -verify %s
22
// RUN: %target-swift-frontend -concurrency-model=task-to-thread -typecheck -verify -verify-additional-prefix complete- -strict-concurrency=complete %s
33

4-
// expected-complete-warning@+3 {{var 'global' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
5-
// expected-complete-note@+2 {{isolate 'global' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
6-
// expected-complete-note@+1 {{var declared here}}
4+
// expected-complete-warning@+5 {{var 'global' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in the Swift 6 language mode}}
5+
// expected-complete-note@+4 {{restrict 'global' to the main actor if it will only be accessed from the main thread}}{{1-1=@MainActor }}
6+
// expected-complete-note@+3 {{var declared here}}
7+
// expected-complete-note@+2 {{unsafely mark 'global' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}{{1-1=nonisolated(unsafe) }}
8+
// expected-complete-note@+1 {{convert 'global' to a 'let' constant to make the shared state immutable}}{{1-4=let}}
79
var global = 10
810

911
// expected-complete-warning@+1 {{reference to var 'global' is not concurrency-safe because it involves shared mutable state; this is an error in the Swift 6 language mode}}

test/Concurrency/global_variables.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ actor TestGlobalActor {
1515
var mutableIsolatedGlobal = 1
1616

1717
var mutableNonisolatedGlobal = 1 // expected-error{{var 'mutableNonisolatedGlobal' is not concurrency-safe because it is non-isolated global shared mutable state}}
18-
// expected-note@-1{{isolate 'mutableNonisolatedGlobal' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
18+
// expected-note@-1{{restrict 'mutableNonisolatedGlobal' to the main actor if it will only be accessed from the main thread}}{{1-1=@MainActor }}
19+
// expected-note@-2{{unsafely mark 'mutableNonisolatedGlobal' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}{{1-1=nonisolated(unsafe) }}
20+
// expected-note@-3{{convert 'mutableNonisolatedGlobal' to a 'let' constant to make the shared state immutable}}{{1-4=let}}
1921

2022
let immutableGlobal = 1
2123

@@ -46,20 +48,26 @@ actor TestActor {
4648
struct TestStatics {
4749
static let immutableExplicitSendable = TestSendable()
4850
static let immutableNonsendable = TestNonsendable() // expected-error{{static property 'immutableNonsendable' is not concurrency-safe because non-'Sendable' type 'TestNonsendable' may have shared mutable state}}
49-
// expected-note@-1 {{isolate 'immutableNonsendable' to a global actor, or conform 'TestNonsendable' to 'Sendable'}}
51+
// expected-note@-1 {{restrict 'immutableNonsendable' to the main actor if it will only be accessed from the main thread}}
52+
// expected-note@-2 {{unsafely mark 'immutableNonsendable' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
5053
static nonisolated(unsafe) let immutableNonisolatedUnsafe = TestNonsendable()
5154
static nonisolated let immutableNonisolated = TestNonsendable() // expected-error{{static property 'immutableNonisolated' is not concurrency-safe because non-'Sendable' type 'TestNonsendable' may have shared mutable state}}
52-
// expected-note@-1 {{isolate 'immutableNonisolated' to a global actor, or conform 'TestNonsendable' to 'Sendable'}}
55+
// expected-note@-1 {{restrict 'immutableNonisolated' to the main actor if it will only be accessed from the main thread}}
5356
// expected-error@-2 {{'nonisolated' can not be applied to variable with non-'Sendable' type 'TestNonsendable'}}
57+
// expected-note@-3{{unsafely mark 'immutableNonisolated' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
5458
static nonisolated(unsafe) let immutableNonisolatedUnsafeSendable = TestSendable()
5559
// expected-warning@-1 {{'nonisolated(unsafe)' is unnecessary for a constant with 'Sendable' type 'TestSendable', consider removing it}} {{10-30=}}
5660
static let immutableInferredSendable = 0
5761
static var mutable = 0 // expected-error{{static property 'mutable' is not concurrency-safe because it is non-isolated global shared mutable state}}
58-
// expected-note@-1{{isolate 'mutable' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
62+
// expected-note@-1{{convert 'mutable' to a 'let' constant to make the shared state immutable}}
5963
// expected-note@-2{{static property declared here}}
64+
// expected-note@-3{{unsafely mark 'mutable' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
65+
// expected-note@-4{{restrict 'mutable' to the main actor if it will only be accessed from the main thread}}
6066
static var computedProperty: Int { 0 } // computed property that, though static, has no storage so is not a global
6167
@TestWrapper static var wrapped: Int // expected-error{{static property 'wrapped' is not concurrency-safe because it is non-isolated global shared mutable state}}
62-
// expected-note@-1{{isolate 'wrapped' to a global actor, or convert it to a 'let' constant and conform it to 'Sendable'}}
68+
// expected-note@-1{{convert 'wrapped' to a 'let' constant to make the shared state immutable}}{{23-26=let}}
69+
// expected-note@-2{{unsafely mark 'wrapped' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}{{16-16=nonisolated(unsafe) }}
70+
// expected-note@-3{{restrict 'wrapped' to the main actor if it will only be accessed from the main thread}}{{3-3=@MainActor }}
6371
}
6472

6573
public actor TestPublicActor {

test/Concurrency/predates_concurrency_import.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func test(
4444
let nonStrictGlobal = NonStrictClass() // no warning
4545

4646
let strictGlobal = StrictStruct() // expected-warning{{let 'strictGlobal' is not concurrency-safe because non-'Sendable' type 'StrictStruct' may have shared mutable state}}
47-
// expected-note@-1{{isolate 'strictGlobal' to a global actor, or conform 'StrictStruct' to 'Sendable'}}
47+
// expected-note@-1{{restrict 'strictGlobal' to the main actor if it will only be accessed from the main thread}}
48+
// expected-note@-2{{unsafely mark 'strictGlobal' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
4849

4950
extension NonStrictClass {
5051
@Sendable func f() { }
@@ -61,7 +62,8 @@ struct HasStatics {
6162
nonisolated static let ss: StrictStruct = StrictStruct()
6263
// expected-warning@-1{{'nonisolated' can not be applied to variable with non-'Sendable' type 'StrictStruct'}}
6364
// expected-warning@-2{{static property 'ss' is not concurrency-safe because non-'Sendable' type 'StrictStruct' may have shared mutable state}}
64-
// expected-note@-3{{isolate 'ss' to a global actor, or conform 'StrictStruct' to 'Sendable'}}
65+
// expected-note@-3{{restrict 'ss' to the main actor if it will only be accessed from the main thread}}
66+
// expected-note@-4{{unsafely mark 'ss' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
6567
}
6668

6769
extension NonStrictClass2: @retroactive MySendableProto { }

test/Concurrency/predates_concurrency_import_swift6.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ func test(ss: StrictStruct, ns: NonStrictClass) {
1818

1919
let nonStrictGlobal = NonStrictClass()
2020
let strictGlobal = StrictStruct() // expected-warning{{let 'strictGlobal' is not concurrency-safe because non-'Sendable' type 'StrictStruct' may have shared mutable state}}
21-
// expected-note@-1{{isolate 'strictGlobal' to a global actor, or conform 'StrictStruct' to 'Sendable'}}
21+
// expected-note@-1{{restrict 'strictGlobal' to the main actor if it will only be accessed from the main thread}}
22+
// expected-note@-2{{unsafely mark 'strictGlobal' as concurrency-safe if all accesses are protected by an external synchronization mechanism}}
2223

2324
extension NonStrictClass {
2425
@Sendable func f() { }

0 commit comments

Comments
 (0)