Skip to content

Commit 8a4cb8b

Browse files
committed
Support actor isolation for property wrappers
Property wrappers can provide global actor isolation based on the property wrapper type itself (which affects the backing property), `wrappedValue` (which affects the wrapped property), and `projectedValue` (which affects the $ property). This allows us to express, e.g., property wrappers that require accesses to occur on a particular global actor.
1 parent 531b080 commit 8a4cb8b

File tree

4 files changed

+214
-3
lines changed

4 files changed

+214
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4406,6 +4406,11 @@ ERROR(actorindependent_local_var,none,
44064406
"'@actorIndependent' can not be applied to local variables",
44074407
())
44084408

4409+
ERROR(actor_instance_property_wrapper,none,
4410+
"%0 property in property wrapper type %1 cannot be isolated to "
4411+
"the actor instance; consider @actorIndependent",
4412+
(Identifier, Identifier))
4413+
44094414
ERROR(concurrency_lib_missing,none,
44104415
"missing '%0' declaration, probably because the '_Concurrency' "
44114416
"module was not imported", (StringRef))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,7 +2344,8 @@ ActorIsolation ActorIsolationRequest::evaluate(
23442344
}
23452345

23462346
// Function used when returning an inferred isolation.
2347-
auto inferredIsolation = [&](ActorIsolation inferred) {
2347+
auto inferredIsolation = [&](
2348+
ActorIsolation inferred, bool propagateUnsafe = false) {
23482349
// Add an implicit attribute to capture the actor isolation that was
23492350
// inferred, so that (e.g.) it will be printed and serialized.
23502351
ASTContext &ctx = value->getASTContext();
@@ -2357,13 +2358,19 @@ ActorIsolation ActorIsolationRequest::evaluate(
23572358
break;
23582359

23592360
case ActorIsolation::GlobalActorUnsafe:
2360-
// Don't infer unsafe global actor isolation.
2361-
return ActorIsolation::forUnspecified();
2361+
if (!propagateUnsafe) {
2362+
// Don't infer unsafe global actor isolation.
2363+
return ActorIsolation::forUnspecified();
2364+
}
2365+
2366+
LLVM_FALLTHROUGH;
23622367

23632368
case ActorIsolation::GlobalActor: {
23642369
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
23652370
auto attr = CustomAttr::create(
23662371
ctx, SourceLoc(), typeExpr, /*implicit=*/true);
2372+
if (inferred == ActorIsolation::GlobalActorUnsafe)
2373+
attr->setArgIsUnsafe(true);
23672374
value->getAttrs().add(attr);
23682375
break;
23692376
}
@@ -2401,6 +2408,46 @@ ActorIsolation ActorIsolationRequest::evaluate(
24012408
}
24022409

24032410
if (shouldInferAttributeInContext(value->getDeclContext())) {
2411+
if (auto var = dyn_cast<VarDecl>(value)) {
2412+
// If this is a variable with a property wrapper, infer from the property
2413+
// wrapper's wrappedValue.
2414+
if (auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(0)) {
2415+
if (auto wrappedValue = wrapperInfo.valueVar) {
2416+
if (auto isolation = getActorIsolation(wrappedValue))
2417+
return inferredIsolation(isolation, /*propagateUnsafe=*/true);
2418+
}
2419+
}
2420+
2421+
// If this is the backing storage for a property wrapper, infer from the
2422+
// type of the outermost property wrapper.
2423+
if (auto originalVar = var->getOriginalWrappedProperty(
2424+
PropertyWrapperSynthesizedPropertyKind::Backing)) {
2425+
if (auto backingType =
2426+
originalVar->getPropertyWrapperBackingPropertyType()) {
2427+
if (auto backingNominal = backingType->getAnyNominal()) {
2428+
if (!isa<ClassDecl>(backingNominal) ||
2429+
!cast<ClassDecl>(backingNominal)->isActor()) {
2430+
if (auto isolation = getActorIsolation(backingNominal))
2431+
return inferredIsolation(isolation, /*propagateUnsafe=*/true);
2432+
}
2433+
}
2434+
}
2435+
}
2436+
2437+
// If this is the projected property for a property wrapper, infer from
2438+
// the property wrapper's projectedValue.
2439+
if (auto originalVar = var->getOriginalWrappedProperty(
2440+
PropertyWrapperSynthesizedPropertyKind::Projection)) {
2441+
if (auto wrapperInfo =
2442+
originalVar->getAttachedPropertyWrapperTypeInfo(0)) {
2443+
if (auto projectedValue = wrapperInfo.projectedValueVar) {
2444+
if (auto isolation = getActorIsolation(projectedValue))
2445+
return inferredIsolation(isolation, /*propagateUnsafe=*/true);
2446+
}
2447+
}
2448+
}
2449+
}
2450+
24042451
// If the declaration witnesses a protocol requirement that is isolated,
24052452
// use that.
24062453
if (auto witnessedIsolation = getIsolationFromWitnessedRequirements(value)) {

lib/Sema/TypeCheckPropertyWrapper.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ static VarDecl *findValueProperty(ASTContext &ctx, NominalTypeDecl *nominal,
8282
return nullptr;
8383
}
8484

85+
// The property must not be isolated to an actor instance.
86+
switch (auto isolation = getActorIsolation(var)) {
87+
case ActorIsolation::ActorInstance:
88+
var->diagnose(
89+
diag::actor_instance_property_wrapper, var->getName(),
90+
nominal->getName());
91+
return nullptr;
92+
93+
case ActorIsolation::GlobalActor:
94+
case ActorIsolation::GlobalActorUnsafe:
95+
case ActorIsolation::Independent:
96+
case ActorIsolation::IndependentUnsafe:
97+
case ActorIsolation::Unspecified:
98+
break;
99+
}
100+
85101
return var;
86102
}
87103

test/Concurrency/global_actor_inference.swift

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,107 @@ func barSync() {
234234
foo() // expected-error {{global function 'foo()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
235235
}
236236

237+
// ----------------------------------------------------------------------
238+
// Property wrappers
239+
// ----------------------------------------------------------------------
240+
241+
@propertyWrapper
242+
@OtherGlobalActor
243+
struct WrapperOnActor<Wrapped> {
244+
@actorIndependent(unsafe) private var stored: Wrapped
245+
246+
init(wrappedValue: Wrapped) {
247+
stored = wrappedValue
248+
}
249+
250+
@MainActor var wrappedValue: Wrapped {
251+
get { stored }
252+
set { stored = newValue }
253+
}
254+
255+
@SomeGlobalActor var projectedValue: Wrapped {
256+
get { stored }
257+
set { stored = newValue }
258+
}
259+
}
260+
261+
@propertyWrapper
262+
actor WrapperActor<Wrapped> {
263+
@actorIndependent(unsafe) var storage: Wrapped
264+
265+
init(wrappedValue: Wrapped) {
266+
storage = wrappedValue
267+
}
268+
269+
@actorIndependent var wrappedValue: Wrapped {
270+
get { storage }
271+
set { storage = newValue }
272+
}
273+
274+
@actorIndependent var projectedValue: Wrapped {
275+
get { storage }
276+
set { storage = newValue }
277+
}
278+
}
279+
280+
struct HasWrapperOnActor {
281+
@WrapperOnActor var synced: Int = 0
282+
// expected-note@-1 3{{mutable state is only available within the actor instance}}
283+
284+
// expected-note@+1 3{{to make instance method 'testErrors()'}}
285+
func testErrors() {
286+
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from this context}}
287+
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
288+
_ = _synced // expected-error{{property '_synced' isolated to global actor 'OtherGlobalActor' can not be referenced from this context}}
289+
}
290+
291+
@MainActor mutating func testOnMain() {
292+
_ = synced
293+
synced = 17
294+
}
295+
296+
@WrapperActor var actorSynced: Int = 0
297+
298+
func testActorSynced() {
299+
_ = actorSynced
300+
_ = $actorSynced
301+
_ = _actorSynced
302+
}
303+
}
304+
305+
306+
@propertyWrapper
307+
actor WrapperActorBad1<Wrapped> {
308+
var storage: Wrapped
309+
310+
init(wrappedValue: Wrapped) {
311+
storage = wrappedValue
312+
}
313+
314+
var wrappedValue: Wrapped { // expected-error{{'wrappedValue' property in property wrapper type 'WrapperActorBad1' cannot be isolated to the actor instance; consider @actorIndependent}}}}
315+
get { storage }
316+
set { storage = newValue }
317+
}
318+
}
319+
320+
@propertyWrapper
321+
actor WrapperActorBad2<Wrapped> {
322+
@actorIndependent(unsafe) var storage: Wrapped
323+
324+
init(wrappedValue: Wrapped) {
325+
storage = wrappedValue
326+
}
327+
328+
@actorIndependent var wrappedValue: Wrapped {
329+
get { storage }
330+
set { storage = newValue }
331+
}
332+
333+
var projectedValue: Wrapped { // expected-error{{'projectedValue' property in property wrapper type 'WrapperActorBad2' cannot be isolated to the actor instance; consider @actorIndependent}}
334+
get { storage }
335+
set { storage = newValue }
336+
}
337+
}
237338

238339
// ----------------------------------------------------------------------
239340
// Unsafe global actors
@@ -266,3 +367,45 @@ class UGASubclass1: UGAClass {
266367
class UGASubclass2: UGAClass {
267368
override func method() { }
268369
}
370+
371+
@propertyWrapper
372+
@OtherGlobalActor(unsafe)
373+
struct WrapperOnUnsafeActor<Wrapped> {
374+
@actorIndependent(unsafe) private var stored: Wrapped
375+
376+
init(wrappedValue: Wrapped) {
377+
stored = wrappedValue
378+
}
379+
380+
@MainActor(unsafe) var wrappedValue: Wrapped {
381+
get { stored }
382+
set { stored = newValue }
383+
}
384+
385+
@SomeGlobalActor(unsafe) var projectedValue: Wrapped {
386+
get { stored }
387+
set { stored = newValue }
388+
}
389+
}
390+
391+
struct HasWrapperOnUnsafeActor {
392+
@WrapperOnUnsafeActor var synced: Int = 0
393+
// expected-note@-1 3{{mutable state is only available within the actor instance}}
394+
395+
func testUnsafeOkay() {
396+
_ = synced
397+
_ = $synced
398+
_ = _synced
399+
}
400+
401+
@actorIndependent func testErrors() {
402+
_ = synced // expected-error{{property 'synced' isolated to global actor 'MainActor' can not be referenced from}}
403+
_ = $synced // expected-error{{property '$synced' isolated to global actor 'SomeGlobalActor' can not be referenced from}}
404+
_ = _synced // expected-error{{property '_synced' isolated to global actor 'OtherGlobalActor' can not be referenced from}}
405+
}
406+
407+
@MainActor mutating func testOnMain() {
408+
_ = synced
409+
synced = 17
410+
}
411+
}

0 commit comments

Comments
 (0)