Skip to content

Commit 19bdfb1

Browse files
committed
In a property-wrapper, global-actor isolation remains on wrappedValue
If a struct is a property-wrapper, then global-actor isolation still applies to the `wrappedValue`, even if it's a stored property. This is needed in order to support the propagation of global-actor isolation through the wrapper, even when the programmer has opted to use a stored property instead of a computed one for the `wrappedValue`. Since this propagation is a useful pattern, I think this exception is reasonable.
1 parent 057e696 commit 19bdfb1

File tree

6 files changed

+110
-7
lines changed

6 files changed

+110
-7
lines changed

lib/Sema/TypeCheckAttr.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5630,8 +5630,10 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
56305630
}
56315631

56325632
// 'nonisolated' is redundant for the stored properties of a struct.
5633-
if (isa<StructDecl>(nominal) && !var->isStatic() &&
5634-
var->isOrdinaryStoredProperty()) {
5633+
if (isa<StructDecl>(nominal) &&
5634+
!var->isStatic() &&
5635+
var->isOrdinaryStoredProperty() &&
5636+
!isWrappedValueOfPropWrapper(var)) {
56355637
diagnoseAndRemoveAttr(attr, diag::nonisolated_storage_value_type,
56365638
nominal->getDescriptiveKind())
56375639
.warnUntilSwiftVersion(6);

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ GlobalActorAttributeRequest::evaluate(
359359
// ... and not if it's the instance storage of a struct
360360
if (!var->isStatic() && var->isOrdinaryStoredProperty()) {
361361
if (auto *nominal = var->getDeclContext()->getSelfNominalTypeDecl()) {
362-
if (isa<StructDecl>(nominal)) {
362+
if (isa<StructDecl>(nominal) && !isWrappedValueOfPropWrapper(var)) {
363363

364364
var->diagnose(diag::global_actor_on_storage_of_value_type,
365365
var->getName(), nominal->getDescriptiveKind())
@@ -3631,7 +3631,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
36313631
if (!var->isStatic() && var->isOrdinaryStoredProperty())
36323632
if (auto *varDC = var->getDeclContext())
36333633
if (auto *nominal = varDC->getSelfNominalTypeDecl())
3634-
if (isa<StructDecl>(nominal))
3634+
if (isa<StructDecl>(nominal) && !isWrappedValueOfPropWrapper(var))
36353635
return ActorIsolation::forUnspecified();
36363636

36373637
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);

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/global_actor_inference.swift

Lines changed: 36 additions & 3 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 {
302+
@MainActor var wrappedValue: Wrapped { // expected-note {{property declared here}}
303303
get { }
304304
set { }
305305
}
@@ -314,7 +314,19 @@ struct WrapperOnActor<Wrapped: Sendable> {
314314
@propertyWrapper
315315
public struct WrapperOnMainActor<Wrapped> {
316316
// Make sure inference of @MainActor on wrappedValue doesn't crash.
317-
public var wrappedValue: Wrapped
317+
318+
public var wrappedValue: Wrapped // expected-note {{property declared here}}
319+
320+
public var accessCount: Int
321+
322+
nonisolated public init(wrappedValue: Wrapped) {
323+
self.wrappedValue = wrappedValue
324+
}
325+
}
326+
327+
@propertyWrapper
328+
public struct WrapperOnMainActor2<Wrapped> {
329+
@MainActor public var wrappedValue: Wrapped
318330

319331
public init(wrappedValue: Wrapped) {
320332
self.wrappedValue = wrappedValue
@@ -340,15 +352,36 @@ actor WrapperActor<Wrapped: Sendable> {
340352
}
341353
}
342354

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+
343375
struct HasWrapperOnActor {
344376
@WrapperOnActor var synced: Int = 0
345377
// expected-note@-1 2{{property declared here}}
346378

347-
// expected-note@+1 2{{to make instance method 'testErrors()'}}
379+
// expected-note@+1 3{{to make instance method 'testErrors()'}}
348380
func testErrors() {
349381
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
350382
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from this synchronous context}}
351383
_ = _synced
384+
_ = _synced.wrappedValue // expected-error{{property 'wrappedValue' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
352385
}
353386

354387
@MainActor mutating func testOnMain() {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.15 -swift-version 5
2+
3+
// REQUIRES: objc_interop
4+
// REQUIRES: OS=macosx
5+
6+
import SwiftUI
7+
8+
class Visibility: ObservableObject { // expected-note {{class 'Visibility' does not conform to the 'Sendable' protocol}}
9+
@Published var yes = false // some nonsense
10+
}
11+
12+
struct CoffeeTrackerView: View { // expected-note{{consider making struct 'CoffeeTrackerView' conform to the 'Sendable' protocol}}
13+
@ObservedObject var showDrinkList: Visibility = Visibility()
14+
15+
var storage: Visibility = Visibility()
16+
17+
var body: some View {
18+
VStack {
19+
Button(action: {}) {
20+
Image(self.showDrinkList.yes ? "add-coffee" : "add-tea")
21+
.renderingMode(.template)
22+
}
23+
}
24+
}
25+
}
26+
27+
@MainActor
28+
func fromMainActor() async {
29+
let view = CoffeeTrackerView()
30+
_ = view.body
31+
_ = view.showDrinkList
32+
_ = view.storage
33+
}
34+
35+
36+
func fromConcurrencyAware() async {
37+
// expected-note@+3 {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
38+
// expected-error@+2 {{expression is 'async' but is not marked with 'await'}}
39+
// expected-warning@+1 {{non-sendable type 'CoffeeTrackerView' returned by call to main actor-isolated function cannot cross actor boundary}}
40+
let view = CoffeeTrackerView()
41+
42+
// expected-note@+3 {{property access is 'async'}}
43+
// expected-warning@+2 {{non-sendable type 'some View' in implicitly asynchronous access to main actor-isolated property 'body' cannot cross actor boundary}}
44+
// expected-error@+1 {{expression is 'async' but is not marked with 'await'}}
45+
_ = view.body
46+
47+
// expected-note@+3 {{property access is 'async'}}
48+
// expected-warning@+2 {{non-sendable type 'Visibility' in implicitly asynchronous access to main actor-isolated property 'showDrinkList' cannot cross actor boundary}}
49+
// expected-error@+1 {{expression is 'async' but is not marked with 'await'}}
50+
_ = view.showDrinkList
51+
52+
_ = view.storage
53+
}

0 commit comments

Comments
 (0)