Skip to content

Commit d556819

Browse files
authored
Merge pull request #40868 from kavon/drop-redundant-isolation
2 parents 3a4bb96 + 95da5a6 commit d556819

16 files changed

+479
-37
lines changed

include/swift/AST/Decl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5240,6 +5240,10 @@ class VarDecl : public AbstractStorageDecl {
52405240
/// Is this an "async let" property?
52415241
bool isAsyncLet() const;
52425242

5243+
/// Is this a stored property that will _not_ trigger any user-defined code
5244+
/// upon any kind of access?
5245+
bool isOrdinaryStoredProperty() const;
5246+
52435247
Introducer getIntroducer() const {
52445248
return Introducer(Bits.VarDecl.Introducer);
52455249
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4573,6 +4573,9 @@ ERROR(distributed_actor_remote_func_implemented_manually,none,
45734573
ERROR(nonisolated_distributed_actor_storage,none,
45744574
"'nonisolated' can not be applied to distributed actor stored properties",
45754575
())
4576+
ERROR(nonisolated_storage_value_type,none,
4577+
"'nonisolated' is redundant on %0's stored properties",
4578+
(DescriptiveDeclKind))
45764579
ERROR(distributed_actor_func_nonisolated, none,
45774580
"cannot declare method %0 as both 'nonisolated' and 'distributed'",
45784581
(DeclName))
@@ -4773,6 +4776,9 @@ ERROR(global_actor_on_actor_class,none,
47734776
"actor %0 cannot have a global actor", (Identifier))
47744777
ERROR(global_actor_on_local_variable,none,
47754778
"local variable %0 cannot have a global actor", (DeclName))
4779+
ERROR(global_actor_on_storage_of_value_type,none,
4780+
"stored property %0 within %1 cannot have a global actor",
4781+
(DeclName, DescriptiveDeclKind))
47764782
ERROR(global_actor_non_unsafe_init,none,
47774783
"global actor attribute %0 argument can only be '(unsafe)'", (Type))
47784784
ERROR(global_actor_non_final_class,none,

lib/AST/Decl.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6304,6 +6304,16 @@ bool VarDecl::isAsyncLet() const {
63046304
return getAttrs().hasAttribute<AsyncAttr>();
63056305
}
63066306

6307+
bool VarDecl::isOrdinaryStoredProperty() const {
6308+
// we assume if it hasAttachedPropertyWrapper, it has no storage.
6309+
//
6310+
// also, we don't expect someone to call this on a local property, so for
6311+
// efficiency we don't check if it's not async-let. feel free to promote
6312+
// the assert into a full-fledged part of the condition if needed.
6313+
assert(!isAsyncLet());
6314+
return hasStorage() && !hasObservers();
6315+
}
6316+
63076317
void ParamDecl::setSpecifier(Specifier specifier) {
63086318
// FIXME: Revisit this; in particular shouldn't __owned parameters be
63096319
// ::Let also?

lib/Sema/TypeCheckAttr.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5605,29 +5605,41 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
56055605
if (auto var = dyn_cast<VarDecl>(D)) {
56065606
// stored properties have limitations as to when they can be nonisolated.
56075607
if (var->hasStorage()) {
5608-
auto nominal = dyn_cast<NominalTypeDecl>(dc);
5609-
5610-
// 'nonisolated' can not be applied to stored properties inside
5611-
// distributed actors. Attempts of nonisolated access would be
5612-
// cross-actor, and that means they might be accessing on a remote actor,
5613-
// in which case the stored property storage does not exist.
5614-
//
5615-
// The synthesized "id" and "actorSystem" are the only exceptions,
5616-
// because the implementation mirrors them.
5617-
if (nominal && nominal->isDistributedActor() &&
5618-
!(var->isImplicit() &&
5619-
(var->getName() == Ctx.Id_id ||
5620-
var->getName() == Ctx.Id_actorSystem))) {
5621-
diagnoseAndRemoveAttr(attr,
5622-
diag::nonisolated_distributed_actor_storage);
5623-
return;
5624-
}
56255608

56265609
// 'nonisolated' can not be applied to mutable stored properties.
56275610
if (var->supportsMutation()) {
56285611
diagnoseAndRemoveAttr(attr, diag::nonisolated_mutable_storage);
56295612
return;
56305613
}
5614+
5615+
if (auto nominal = dyn_cast<NominalTypeDecl>(dc)) {
5616+
// 'nonisolated' can not be applied to stored properties inside
5617+
// distributed actors. Attempts of nonisolated access would be
5618+
// cross-actor, which means they might be accessing on a remote actor,
5619+
// in which case the stored property storage does not exist.
5620+
//
5621+
// The synthesized "id" and "actorSystem" are the only exceptions,
5622+
// because the implementation mirrors them.
5623+
if (nominal->isDistributedActor() &&
5624+
!(var->isImplicit() &&
5625+
(var->getName() == Ctx.Id_id ||
5626+
var->getName() == Ctx.Id_actorSystem))) {
5627+
diagnoseAndRemoveAttr(attr,
5628+
diag::nonisolated_distributed_actor_storage);
5629+
return;
5630+
}
5631+
5632+
// 'nonisolated' is redundant for the stored properties of a struct.
5633+
if (isa<StructDecl>(nominal) &&
5634+
!var->isStatic() &&
5635+
var->isOrdinaryStoredProperty() &&
5636+
!isWrappedValueOfPropWrapper(var)) {
5637+
diagnoseAndRemoveAttr(attr, diag::nonisolated_storage_value_type,
5638+
nominal->getDescriptiveKind())
5639+
.warnUntilSwiftVersion(6);
5640+
return;
5641+
}
5642+
}
56315643
}
56325644

56335645
// nonisolated can not be applied to local properties.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,37 @@ GlobalActorAttributeRequest::evaluate(
341341
} else if (auto storage = dyn_cast<AbstractStorageDecl>(decl)) {
342342
// Subscripts and properties are fine...
343343
if (auto var = dyn_cast<VarDecl>(storage)) {
344+
345+
// ... but not if it's an async-context top-level global
344346
if (var->isTopLevelGlobal() && var->getDeclContext()->isAsyncContext()) {
345347
var->diagnose(diag::global_actor_top_level_var)
346348
.highlight(globalActorAttr->getRangeWithAt());
347349
return None;
348-
} else if (var->getDeclContext()->isLocalContext()) {
350+
}
351+
352+
// ... and not if it's local property
353+
if (var->getDeclContext()->isLocalContext()) {
349354
var->diagnose(diag::global_actor_on_local_variable, var->getName())
350355
.highlight(globalActorAttr->getRangeWithAt());
351356
return None;
352357
}
358+
359+
// ... and not if it's the instance storage of a struct
360+
if (!var->isStatic() && var->isOrdinaryStoredProperty()) {
361+
if (auto *nominal = var->getDeclContext()->getSelfNominalTypeDecl()) {
362+
if (isa<StructDecl>(nominal) && !isWrappedValueOfPropWrapper(var)) {
363+
364+
var->diagnose(diag::global_actor_on_storage_of_value_type,
365+
var->getName(), nominal->getDescriptiveKind())
366+
.highlight(globalActorAttr->getRangeWithAt())
367+
.warnUntilSwiftVersion(6);
368+
369+
// In Swift 6, once the diag above is an error, it is disallowed.
370+
if (var->getASTContext().isSwiftVersionAtLeast(6))
371+
return None;
372+
}
373+
}
374+
}
353375
}
354376
} else if (isa<ExtensionDecl>(decl)) {
355377
// Extensions are okay.
@@ -3604,6 +3626,16 @@ ActorIsolation ActorIsolationRequest::evaluate(
36043626

36053627
case ActorIsolation::GlobalActorUnsafe:
36063628
case ActorIsolation::GlobalActor: {
3629+
// Stored properties of a struct don't need global-actor isolation.
3630+
if (ctx.isSwiftVersionAtLeast(6))
3631+
if (auto *var = dyn_cast<VarDecl>(value))
3632+
if (!var->isStatic() && var->isOrdinaryStoredProperty())
3633+
if (auto *varDC = var->getDeclContext())
3634+
if (auto *nominal = varDC->getSelfNominalTypeDecl())
3635+
if (isa<StructDecl>(nominal) &&
3636+
!isWrappedValueOfPropWrapper(var))
3637+
return ActorIsolation::forUnspecified();
3638+
36073639
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
36083640
auto attr = CustomAttr::create(
36093641
ctx, SourceLoc(), typeExpr, /*implicit=*/true);

lib/Sema/TypeCheckPropertyWrapper.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,3 +799,14 @@ Expr *swift::buildPropertyWrapperInitCall(
799799

800800
return initializer;
801801
}
802+
803+
bool swift::isWrappedValueOfPropWrapper(VarDecl *var) {
804+
if (!var->isStatic())
805+
if (auto *dc = var->getDeclContext())
806+
if (auto *nominal = dc->getSelfNominalTypeDecl())
807+
if (nominal->getAttrs().hasAttribute<PropertyWrapperAttr>())
808+
if (var->getName() == var->getASTContext().Id_wrappedValue)
809+
return true;
810+
811+
return false;
812+
}

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,10 @@ Expr *buildPropertyWrapperInitCall(
12761276
PropertyWrapperInitKind initKind,
12771277
llvm::function_ref<void(ApplyExpr *)> callback = [](ApplyExpr *) {});
12781278

1279+
/// Check if this var is the \c wrappedValue property belonging to
1280+
/// a property wrapper type declaration.
1281+
bool isWrappedValueOfPropWrapper(VarDecl *var);
1282+
12791283
/// Whether an overriding declaration requires the 'override' keyword.
12801284
enum class OverrideRequiresKeyword {
12811285
/// The keyword is never required.

test/Concurrency/Inputs/dynamically_replaceable.swift

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
// dynamically_replaceable
3+
@MainActor
4+
public dynamic func dynamicOnMainActor() { }
5+
6+
// property wrapper + main actor
7+
@propertyWrapper
8+
public struct IntWrapper {
9+
10+
public init(wrappedValue: Int) {
11+
self.wrappedValue = wrappedValue
12+
}
13+
14+
@MainActor public var wrappedValue: Int
15+
}

test/Concurrency/actor_isolation.swift

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ actor MySuperActor {
4646
}
4747
}
4848

49-
class Point { // expected-note{{class 'Point' does not conform to the 'Sendable' protocol}}
49+
class Point { // expected-note 5{{class 'Point' does not conform to the 'Sendable' protocol}}
5050
var x : Int = 0
5151
var y : Int = 0
5252
}
@@ -109,6 +109,66 @@ func checkAsyncPropertyAccess() async {
109109
_ = act.point // expected-warning{{non-sendable type 'Point' in asynchronous access to actor-isolated property 'point' cannot cross actor boundary}}
110110
}
111111

112+
/// ------------------------------------------------------------------
113+
/// -- Value types do not need isolation on their stored properties --
114+
protocol MainCounter {
115+
@MainActor var counter: Int { get set }
116+
@MainActor var ticker: Int { get set }
117+
}
118+
119+
struct InferredFromConformance: MainCounter {
120+
var counter = 0
121+
var ticker: Int {
122+
get { 1 }
123+
set {}
124+
}
125+
}
126+
127+
@MainActor
128+
struct InferredFromContext {
129+
var point = Point()
130+
var polygon: [Point] {
131+
get { [] }
132+
}
133+
134+
nonisolated var status: Bool = true // expected-error {{'nonisolated' can not be applied to stored properties}}{{3-15=}}
135+
136+
nonisolated let flag: Bool = false // expected-warning {{'nonisolated' is redundant on struct's stored properties; this is an error in Swift 6}}{{3-15=}}
137+
138+
subscript(_ i: Int) -> Int { return i }
139+
140+
static var stuff: [Int] = []
141+
}
142+
143+
func checkIsolationValueType(_ formance: InferredFromConformance,
144+
_ ext: InferredFromContext,
145+
_ anno: NoGlobalActorValueType) async {
146+
// these still do need an await in Swift 5
147+
_ = await ext.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to main actor-isolated property 'point' cannot cross actor boundary}}
148+
_ = await formance.counter
149+
_ = await anno.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
150+
_ = anno.counter
151+
152+
// these will always need an await
153+
_ = await (formance as MainCounter).counter
154+
_ = await ext[1]
155+
_ = await formance.ticker
156+
_ = await ext.polygon // expected-warning {{non-sendable type '[Point]' in implicitly asynchronous access to main actor-isolated property 'polygon' cannot cross actor boundary}}
157+
_ = await InferredFromContext.stuff
158+
_ = await NoGlobalActorValueType.polygon // expected-warning {{non-sendable type '[Point]' in implicitly asynchronous access to main actor-isolated static property 'polygon' cannot cross actor boundary}}
159+
}
160+
161+
// check for instance members that do not need global-actor protection
162+
struct NoGlobalActorValueType {
163+
@SomeGlobalActor var point: Point // expected-warning {{stored property 'point' within struct cannot have a global actor; this is an error in Swift 6}}
164+
165+
@MainActor let counter: Int // expected-warning {{stored property 'counter' within struct cannot have a global actor; this is an error in Swift 6}}
166+
167+
@MainActor static var polygon: [Point] = []
168+
}
169+
170+
/// -----------------------------------------------------------------
171+
112172
@available(SwiftStdlib 5.1, *)
113173
extension MyActor {
114174
nonisolated var actorIndependentVar: Int {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -warn-concurrency -swift-version 6
2+
// REQUIRES: concurrency
3+
4+
final class ImmutablePoint: Sendable {
5+
let x : Int = 0
6+
let y : Int = 0
7+
}
8+
9+
actor SomeActor { }
10+
11+
@globalActor
12+
struct SomeGlobalActor {
13+
static let shared = SomeActor()
14+
}
15+
16+
/// ------------------------------------------------------------------
17+
/// -- Value types do not need isolation on their stored properties --
18+
protocol MainCounter {
19+
@MainActor var counter: Int { get set }
20+
@MainActor var ticker: Int { get set }
21+
}
22+
23+
struct InferredFromConformance: MainCounter {
24+
var counter = 0
25+
var ticker: Int {
26+
get { 1 }
27+
set {}
28+
}
29+
}
30+
31+
@MainActor
32+
struct InferredFromContext {
33+
var point = ImmutablePoint()
34+
var polygon: [ImmutablePoint] {
35+
get { [] }
36+
}
37+
38+
nonisolated let flag: Bool = false // expected-error {{'nonisolated' is redundant on struct's stored properties}}{{3-15=}}
39+
40+
subscript(_ i: Int) -> Int { return i }
41+
42+
static var stuff: [Int] = []
43+
}
44+
45+
func checkIsolationValueType(_ formance: InferredFromConformance,
46+
_ ext: InferredFromContext,
47+
_ anno: NoGlobalActorValueType) async {
48+
// these do not need an await, since it's a value type
49+
_ = ext.point
50+
_ = formance.counter
51+
_ = anno.point
52+
_ = anno.counter
53+
54+
// make sure it's just a warning if someone was awaiting on it previously
55+
_ = await ext.point // expected-warning {{no 'async' operations occur within 'await' expression}}
56+
_ = await formance.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
57+
_ = await anno.point // expected-warning {{no 'async' operations occur within 'await' expression}}
58+
_ = await anno.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
59+
60+
// these do need await, regardless of reference or value type
61+
_ = await (formance as MainCounter).counter
62+
_ = await ext[1]
63+
_ = await formance.ticker
64+
_ = await ext.polygon
65+
_ = await InferredFromContext.stuff
66+
_ = await NoGlobalActorValueType.polygon
67+
}
68+
69+
// check for instance members that do not need global-actor protection
70+
struct NoGlobalActorValueType {
71+
@SomeGlobalActor var point: ImmutablePoint // expected-error {{stored property 'point' within struct cannot have a global actor}}
72+
73+
@MainActor let counter: Int // expected-error {{stored property 'counter' within struct cannot have a global actor}}
74+
75+
@MainActor static var polygon: [ImmutablePoint] = []
76+
}
77+
78+
/// -----------------------------------------------------------------

0 commit comments

Comments
 (0)