Skip to content

Commit 13dd3d9

Browse files
committed
have the inferred isolation for properties change only in Swift 6
This patch delays the removal of redundant isolation for inferred global-actor isolation to Swift 6 too, since we only warn about it changing in Swift 5. Otherwise, only isolation that is a byproduct of inference no longer needs an await, which will probably confuse people. This change is with respect to SE-327, which argues that the non-static stored properties of ordinary structs do not need global-actor isolation.
1 parent 19bdfb1 commit 13dd3d9

File tree

7 files changed

+233
-53
lines changed

7 files changed

+233
-53
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3627,12 +3627,14 @@ ActorIsolation ActorIsolationRequest::evaluate(
36273627
case ActorIsolation::GlobalActorUnsafe:
36283628
case ActorIsolation::GlobalActor: {
36293629
// Stored properties of a struct don't need global-actor isolation.
3630-
if (auto *var = dyn_cast<VarDecl>(value))
3631-
if (!var->isStatic() && var->isOrdinaryStoredProperty())
3632-
if (auto *varDC = var->getDeclContext())
3633-
if (auto *nominal = varDC->getSelfNominalTypeDecl())
3634-
if (isa<StructDecl>(nominal) && !isWrappedValueOfPropWrapper(var))
3635-
return ActorIsolation::forUnspecified();
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();
36363638

36373639
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
36383640
auto attr = CustomAttr::create(

test/Concurrency/actor_isolation.swift

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ actor MySuperActor {
4646
}
4747
}
4848

49-
class Point { // expected-note 3{{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
}
@@ -111,14 +111,11 @@ func checkAsyncPropertyAccess() async {
111111

112112
/// ------------------------------------------------------------------
113113
/// -- Value types do not need isolation on their stored properties --
114-
115-
@available(SwiftStdlib 5.1, *)
116114
protocol MainCounter {
117115
@MainActor var counter: Int { get set }
118116
@MainActor var ticker: Int { get set }
119117
}
120118

121-
@available(SwiftStdlib 5.1, *)
122119
struct InferredFromConformance: MainCounter {
123120
var counter = 0
124121
var ticker: Int {
@@ -128,7 +125,6 @@ struct InferredFromConformance: MainCounter {
128125
}
129126

130127
@MainActor
131-
@available(SwiftStdlib 5.1, *)
132128
struct InferredFromContext {
133129
var point = Point()
134130
var polygon: [Point] {
@@ -144,23 +140,16 @@ struct InferredFromContext {
144140
static var stuff: [Int] = []
145141
}
146142

147-
@available(SwiftStdlib 5.1, *)
148143
func checkIsolationValueType(_ formance: InferredFromConformance,
149144
_ ext: InferredFromContext,
150145
_ anno: NoGlobalActorValueType) async {
151-
// these do not need an await, since it's a value type
152-
_ = ext.point
153-
_ = formance.counter
154-
_ = anno.point
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}}
155150
_ = anno.counter
156151

157-
// make sure it's just a warning if someone was awaiting on it previously
158-
_ = await ext.point // expected-warning {{no 'async' operations occur within 'await' expression}}
159-
_ = await formance.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
160-
_ = await anno.point // expected-warning {{no 'async' operations occur within 'await' expression}}
161-
_ = await anno.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
162-
163-
// these do need await, regardless of reference or value type
152+
// these will always need an await
164153
_ = await (formance as MainCounter).counter
165154
_ = await ext[1]
166155
_ = await formance.ticker
@@ -170,7 +159,6 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
170159
}
171160

172161
// check for instance members that do not need global-actor protection
173-
@available(SwiftStdlib 5.1, *)
174162
struct NoGlobalActorValueType {
175163
@SomeGlobalActor var point: Point // expected-warning {{stored property 'point' within struct cannot have a global actor; this is an error in Swift 6}}
176164

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+
/// -----------------------------------------------------------------

test/Concurrency/global_actor_inference.swift

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ struct WrapperOnActor<Wrapped: Sendable> {
299299
stored = wrappedValue
300300
}
301301

302-
@MainActor var wrappedValue: Wrapped { // expected-note {{property declared here}}
302+
@MainActor var wrappedValue: Wrapped {
303303
get { }
304304
set { }
305305
}
@@ -315,7 +315,7 @@ struct WrapperOnActor<Wrapped: Sendable> {
315315
public struct WrapperOnMainActor<Wrapped> {
316316
// Make sure inference of @MainActor on wrappedValue doesn't crash.
317317

318-
public var wrappedValue: Wrapped // expected-note {{property declared here}}
318+
public var wrappedValue: Wrapped
319319

320320
public var accessCount: Int
321321

@@ -352,36 +352,15 @@ actor WrapperActor<Wrapped: Sendable> {
352352
}
353353
}
354354

355-
struct HasMainActorWrappedProp {
356-
@WrapperOnMainActor var thing: Int = 1 // expected-note {{property declared here}}
357-
358-
var plainStorage: Int
359-
360-
var computedProp: Int { 0 } // expected-note {{property declared here}}
361-
362-
nonisolated func testErrors() {
363-
_ = thing // expected-error {{property 'thing' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
364-
_ = _thing.wrappedValue // expected-error {{property 'wrappedValue' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
365-
366-
_ = _thing
367-
_ = _thing.accessCount
368-
369-
_ = plainStorage
370-
371-
_ = computedProp // expected-error {{property 'computedProp' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
372-
}
373-
}
374-
375355
struct HasWrapperOnActor {
376356
@WrapperOnActor var synced: Int = 0
377-
// expected-note@-1 2{{property declared here}}
357+
// expected-note@-1 3{{property declared here}}
378358

379359
// expected-note@+1 3{{to make instance method 'testErrors()'}}
380360
func testErrors() {
381361
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
382362
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
383-
_ = _synced
384-
_ = _synced.wrappedValue // expected-error{{property 'wrappedValue' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
363+
_ = _synced // expected-error{{property '_synced' isolated to global actor 'OtherGlobalActor' can not be referenced from this synchronous context}}
385364
}
386365

387366
@MainActor mutating func testOnMain() {
@@ -540,7 +519,7 @@ struct WrapperOnUnsafeActor<Wrapped> {
540519

541520
struct HasWrapperOnUnsafeActor {
542521
@WrapperOnUnsafeActor var synced: Int = 0
543-
// expected-note@-1 2{{property declared here}}
522+
// expected-note@-1 3{{property declared here}}
544523

545524
func testUnsafeOkay() {
546525
_ = synced
@@ -551,7 +530,7 @@ struct HasWrapperOnUnsafeActor {
551530
nonisolated func testErrors() {
552531
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from}}
553532
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from}}
554-
_ = _synced
533+
_ = _synced // expected-error{{property '_synced' isolated to global actor 'OtherGlobalActor' can not be referenced from a non-isolated synchronous context}}
555534
}
556535

557536
@MainActor mutating func testOnMain() {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -swift-version 6 -emit-module -emit-module-path %t/other_global_actor_inference.swiftmodule -module-name other_global_actor_inference -warn-concurrency %S/Inputs/other_global_actor_inference.swift
3+
// RUN: %target-typecheck-verify-swift -swift-version 6 -I %t -disable-availability-checking
4+
// REQUIRES: concurrency
5+
import other_global_actor_inference
6+
7+
actor SomeActor { }
8+
9+
@globalActor
10+
struct SomeGlobalActor {
11+
static let shared = SomeActor()
12+
}
13+
14+
@globalActor
15+
struct OtherGlobalActor {
16+
static let shared = SomeActor()
17+
}
18+
19+
20+
// MARK: Property Wrappers
21+
22+
@propertyWrapper
23+
actor WrapperActor<Wrapped: Sendable> {
24+
var storage: Wrapped
25+
26+
init(wrappedValue: Wrapped) {
27+
storage = wrappedValue
28+
}
29+
30+
nonisolated var wrappedValue: Wrapped {
31+
get { }
32+
set { }
33+
}
34+
35+
nonisolated var projectedValue: Wrapped {
36+
get { }
37+
set { }
38+
}
39+
}
40+
41+
@propertyWrapper
42+
@OtherGlobalActor
43+
struct WrapperOnActor<Wrapped: Sendable> {
44+
private var stored: Wrapped
45+
46+
nonisolated init(wrappedValue: Wrapped) {
47+
stored = wrappedValue
48+
}
49+
50+
@MainActor var wrappedValue: Wrapped { // expected-note {{property declared here}}
51+
get { }
52+
set { }
53+
}
54+
55+
@SomeGlobalActor var projectedValue: Wrapped {
56+
get { }
57+
set { }
58+
}
59+
}
60+
61+
@MainActor
62+
@propertyWrapper
63+
public struct WrapperOnMainActor<Wrapped> {
64+
// Make sure inference of @MainActor on wrappedValue doesn't crash.
65+
66+
public var wrappedValue: Wrapped // expected-note {{property declared here}}
67+
68+
public var accessCount: Int
69+
70+
nonisolated public init(wrappedValue: Wrapped) {
71+
self.wrappedValue = wrappedValue
72+
}
73+
}
74+
75+
struct HasMainActorWrappedProp {
76+
@WrapperOnMainActor var thing: Int = 1 // expected-note {{property declared here}}
77+
78+
var plainStorage: Int
79+
80+
var computedProp: Int { 0 } // expected-note {{property declared here}}
81+
82+
nonisolated func testErrors() {
83+
_ = thing // expected-error {{property 'thing' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
84+
_ = _thing.wrappedValue // expected-error {{property 'wrappedValue' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
85+
86+
_ = _thing
87+
_ = _thing.accessCount
88+
89+
_ = plainStorage
90+
91+
_ = computedProp // expected-error {{property 'computedProp' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
92+
}
93+
}
94+
95+
struct HasWrapperOnActor {
96+
@WrapperOnActor var synced: Int = 0
97+
// expected-note@-1 2{{property declared here}}
98+
99+
// expected-note@+1 3{{to make instance method 'testErrors()'}}
100+
func testErrors() {
101+
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
102+
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
103+
_ = _synced
104+
_ = _synced.wrappedValue // expected-error{{property 'wrappedValue' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
105+
}
106+
107+
@MainActor mutating func testOnMain() {
108+
_ = synced
109+
synced = 17
110+
}
111+
112+
@WrapperActor var actorSynced: Int = 0
113+
114+
func testActorSynced() {
115+
_ = actorSynced
116+
_ = $actorSynced
117+
_ = _actorSynced
118+
}
119+
}
120+
121+
struct Carbon {
122+
@IntWrapper var atomicWeight: Int // expected-note {{property declared here}}
123+
124+
nonisolated func getWeight() -> Int {
125+
return atomicWeight // expected-error {{property 'atomicWeight' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context}}
126+
}
127+
}

test/SILGen/hop_to_executor_async_prop.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,11 @@ struct Container {
602602
// CHECK: [[SOME_BB]]:
603603
// CHECK: [[DATA_ADDR:%[0-9]+]] = unchecked_take_enum_data_addr [[ACCESS]] : $*Optional<Container>, #Optional.some!enumelt
604604
// CHECK: [[ELEM_ADDR:%[0-9]+]] = struct_element_addr [[DATA_ADDR]] : $*Container, #Container.iso
605+
// CHECK: [[PREV_AGAIN:%[0-9]+]] = builtin "getCurrentExecutor"() : $Optional<Builtin.Executor>
606+
// CHECK: hop_to_executor {{%[0-9]+}} : $Cat
605607
// CHECK: {{%[0-9]+}} = load [trivial] [[ELEM_ADDR]] : $*Float
606608
// CHECK: hop_to_executor [[PREV]] : $Optional<Builtin.Executor>
609+
// CHECK: hop_to_executor [[PREV_AGAIN]] : $Optional<Builtin.Executor>
607610
// CHECK: } // end sil function '$s4test9ContainerV10getOrCrashSfyYaFZ'
608611
static func getOrCrash() async -> Float {
609612
return await this!.iso
@@ -628,8 +631,11 @@ struct Container {
628631
// CHECK: [[SOME_BB]]:
629632
// CHECK: [[DATA_ADDR:%[0-9]+]] = unchecked_take_enum_data_addr [[ACCESS]] : $*Optional<Container>, #Optional.some!enumelt
630633
// CHECK: [[ELEM_ADDR:%[0-9]+]] = struct_element_addr [[DATA_ADDR]] : $*Container, #Container.iso
634+
// CHECK: [[PREV_AGAIN:%[0-9]+]] = builtin "getCurrentExecutor"() : $Optional<Builtin.Executor>
635+
// CHECK: hop_to_executor {{%[0-9]+}} : $Cat
631636
// CHECK: {{%[0-9]+}} = load [copy] [[ELEM_ADDR]] : $*CatBox
632637
// CHECK: hop_to_executor [[PREV]] : $Optional<Builtin.Executor>
638+
// CHECK: hop_to_executor [[PREV_AGAIN]] : $Optional<Builtin.Executor>
633639
// CHECK: } // end sil function '$s4test9ContainerV13getRefOrCrashAA6CatBoxCyYaFZ'
634640
static func getRefOrCrash() async -> CatBox {
635641
return await this!.isoRef

0 commit comments

Comments
 (0)