Skip to content

Commit 1861375

Browse files
authored
[Concurrency] Suggest adding a method, when mutating actor property cross isolation (#75922)
1 parent b1a88be commit 1861375

11 files changed

+119
-50
lines changed

include/swift/AST/ActorIsolation.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,7 @@ class ActorIsolation {
9191

9292
ActorIsolation(Kind kind, Expr *actor, unsigned parameterIndex);
9393

94-
ActorIsolation(Kind kind, Type globalActor)
95-
: globalActor(globalActor), kind(kind), isolatedByPreconcurrency(false),
96-
silParsed(false), parameterIndex(0) {}
94+
ActorIsolation(Kind kind, Type globalActor);
9795

9896
public:
9997
// No-argument constructor needed for DenseMap use in PostfixCompletion.cpp
@@ -203,7 +201,6 @@ class ActorIsolation {
203201
}
204202

205203
NominalTypeDecl *getActor() const;
206-
NominalTypeDecl *getActorOrNullPtr() const;
207204

208205
VarDecl *getActorInstance() const;
209206

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5456,6 +5456,9 @@ ERROR(distributed_actor_isolated_non_self_reference,none,
54565456
"distributed actor-isolated %kind0 can not be accessed from a "
54575457
"nonisolated context",
54585458
(const ValueDecl *))
5459+
NOTE(note_consider_method_for_isolated_property_mutation,none,
5460+
"consider declaring an isolated method on %0 to perform the mutation",
5461+
(const NominalTypeDecl *))
54595462
ERROR(conflicting_stored_property_isolation,none,
54605463
"%select{default|memberwise}0 initializer for %1 cannot be both %2 and %3",
54615464
(bool, Type, ActorIsolation, ActorIsolation))

lib/AST/Decl.cpp

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11659,6 +11659,10 @@ ActorIsolation::ActorIsolation(Kind kind, Expr *actor,
1165911659
: actorInstance(actor), kind(kind), isolatedByPreconcurrency(false),
1166011660
silParsed(false), parameterIndex(parameterIndex) {}
1166111661

11662+
ActorIsolation::ActorIsolation(Kind kind, Type globalActor)
11663+
: globalActor(globalActor), kind(kind), isolatedByPreconcurrency(false),
11664+
silParsed(false), parameterIndex(0) {}
11665+
1166211666
ActorIsolation
1166311667
ActorIsolation::forActorInstanceParameter(Expr *actor,
1166411668
unsigned parameterIndex) {
@@ -11709,51 +11713,29 @@ ActorIsolation ActorIsolation::forActorInstanceSelf(NominalTypeDecl *selfDecl) {
1170911713
}
1171011714

1171111715
NominalTypeDecl *ActorIsolation::getActor() const {
11712-
assert(getKind() == ActorInstance ||
11713-
getKind() == GlobalActor);
11716+
assert(getKind() == ActorInstance || getKind() == GlobalActor);
1171411717

1171511718
if (silParsed)
1171611719
return nullptr;
1171711720

11718-
Type actorType;
11719-
11720-
if (auto *instance = actorInstance.dyn_cast<VarDecl *>()) {
11721-
actorType = instance->getTypeInContext();
11722-
} else if (auto *instance = actorInstance.dyn_cast<Expr *>()) {
11723-
actorType = instance->getType();
11724-
}
11725-
11726-
if (actorType) {
11727-
if (auto wrapped = actorType->getOptionalObjectType()) {
11728-
actorType = wrapped;
11729-
}
11730-
return actorType
11731-
->getReferenceStorageReferent()->getAnyActor();
11721+
if (getKind() == GlobalActor) {
11722+
return getGlobalActor()->getAnyNominal();
1173211723
}
1173311724

11734-
return actorInstance.get<NominalTypeDecl *>();
11735-
}
11736-
11737-
NominalTypeDecl *ActorIsolation::getActorOrNullPtr() const {
11738-
if (getKind() != ActorInstance || getKind() != GlobalActor)
11739-
return nullptr;
11740-
11741-
if (silParsed)
11742-
return nullptr;
11743-
1174411725
Type actorType;
1174511726

1174611727
if (auto *instance = actorInstance.dyn_cast<VarDecl *>()) {
1174711728
actorType = instance->getTypeInContext();
11748-
} else if (auto *instance = actorInstance.dyn_cast<Expr *>()) {
11749-
actorType = instance->getType();
11729+
} else if (auto *expr = actorInstance.dyn_cast<Expr *>()) {
11730+
actorType = expr->getType();
1175011731
}
1175111732

1175211733
if (actorType) {
1175311734
if (auto wrapped = actorType->getOptionalObjectType()) {
1175411735
actorType = wrapped;
1175511736
}
11756-
return actorType->getReferenceStorageReferent()->getAnyActor();
11737+
return actorType
11738+
->getReferenceStorageReferent()->getAnyActor();
1175711739
}
1175811740

1175911741
return actorInstance.get<NominalTypeDecl *>();
@@ -11793,7 +11775,10 @@ bool ActorIsolation::isDistributedActor() const {
1179311775
if (silParsed)
1179411776
return false;
1179511777

11796-
return getKind() == ActorInstance && getActor()->isDistributedActor();
11778+
if (getKind() != ActorInstance)
11779+
return false;
11780+
11781+
return getActor()->isDistributedActor();
1179711782
}
1179811783

1179911784
bool ActorIsolation::isEqual(const ActorIsolation &lhs,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,6 +1823,54 @@ static bool wasLegacyEscapingUseRestriction(AbstractFunctionDecl *fn) {
18231823
return !(fn->hasAsync()); // basic case: not async = had restriction.
18241824
}
18251825

1826+
/// Note that while a direct access to the actor isolated property is not legal
1827+
/// you may want to consider introducing an accessing method for a mutation.
1828+
static void
1829+
maybeNoteMutatingMethodSuggestion(ASTContext &C,
1830+
ValueDecl const *member,
1831+
SourceLoc memberLoc,
1832+
DeclContext const *refCxt,
1833+
ActorIsolation isolation,
1834+
std::optional<VarRefUseEnv> useKind) {
1835+
if (!member || !isa<VarDecl>(member))
1836+
return; // we only offer the note property mutations
1837+
1838+
if (!(isolation.getKind() == ActorIsolation::Kind::ActorInstance ||
1839+
isolation.getKind() == ActorIsolation::Kind::GlobalActor)) {
1840+
return;
1841+
}
1842+
1843+
if (useKind != VarRefUseEnv::Mutating) {
1844+
// This note is tailored for the 'mutating' access, i.e. when
1845+
// attempting to mutate a property, they should instead make an actor method to perform the mutation. Reading properties does not have the same restriction.
1846+
return;
1847+
}
1848+
1849+
if (!refCxt->isAsyncContext()) {
1850+
// don't suggest creating method when in sync context, as we won't be able
1851+
// to invoke it anyway so this would not be helpful to suggest
1852+
return;
1853+
}
1854+
1855+
if (auto actor = isolation.getActor()) {
1856+
NominalTypeDecl *actorTy =
1857+
actor;
1858+
// (isolation.getKind() == swift::ActorIsolation::ActorInstance ?
1859+
//)
1860+
1861+
C.Diags.diagnose(
1862+
memberLoc,
1863+
diag::note_consider_method_for_isolated_property_mutation,
1864+
actor);
1865+
// } else if (isolation.getKind() == swift::ActorIsolation::GlobalActor) {
1866+
// C.Diags.diagnose(
1867+
// memberLoc,
1868+
// diag::note_consider_method_for_global_actor_isolated_property_mutation,
1869+
// isolation.getGlobalActor());
1870+
// }
1871+
}
1872+
}
1873+
18261874
/// Note that the given actor member is isolated.
18271875
static void noteIsolatedActorMember(ValueDecl const *decl,
18281876
std::optional<VarRefUseEnv> useKind) {
@@ -1944,20 +1992,23 @@ static bool memberAccessHasSpecialPermissionInSwift5(
19441992
}
19451993

19461994
// Otherwise, it's definitely going to be illegal, so warn and permit.
1947-
auto &diags = refCxt->getASTContext().Diags;
1995+
auto &C = refCxt->getASTContext();
1996+
auto &diags = C.Diags;
19481997
auto useKindInt = static_cast<unsigned>(
19491998
useKind.value_or(VarRefUseEnv::Read));
19501999

2000+
auto isolation = getActorIsolation(const_cast<ValueDecl *>(member));
19512001
diags.diagnose(
19522002
memberLoc, diag::actor_isolated_non_self_reference,
19532003
member,
19542004
useKindInt,
19552005
baseActor.kind + 1,
19562006
baseActor.globalActor,
1957-
getActorIsolation(const_cast<ValueDecl *>(member)))
2007+
isolation)
19582008
.warnUntilSwiftVersion(6);
19592009

19602010
noteIsolatedActorMember(member, useKind);
2011+
maybeNoteMutatingMethodSuggestion(C, member, memberLoc, refCxt, isolation, useKind);
19612012
return true;
19622013
}
19632014

@@ -4438,6 +4489,8 @@ namespace {
44384489
refKind + 1, refGlobalActor,
44394490
result.isolation)
44404491
.warnUntilSwiftVersionIf(preconcurrencyContext, 6);
4492+
maybeNoteMutatingMethodSuggestion(ctx, decl, loc, getDeclContext(), result.isolation,
4493+
kindOfUsage(decl, context).value_or(VarRefUseEnv::Read));
44414494

44424495
if (derivedConformanceType) {
44434496
auto *decl = dyn_cast<ValueDecl>(getDeclContext()->getAsDecl());

test/Concurrency/Inputs/GlobalVariables.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ public struct Globals {
22
public static let integerConstant = 0
33
public static var integerMutable = 0
44

5-
public static nonisolated(unsafe) let nonisolatedUnsafeIntegerConstant = 0
5+
public static let nonisolatedUnsafeIntegerConstant = 0
66
public static nonisolated(unsafe) var nonisolatedUnsafeIntegerMutable = 0
77

88
@MainActor public static var actorInteger = 0

test/Concurrency/actor_call_implicitly_async.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,9 @@ func anotherAsyncFunc() async {
232232

233233
_ = b.balance // expected-error {{actor-isolated instance method 'balance()' can not be partially applied}}
234234

235-
a.owner = "cat" // expected-error{{actor-isolated property 'owner' can not be mutated from a nonisolated context}}
235+
// expected-error@+2{{actor-isolated property 'owner' can not be mutated from a nonisolated context}}
236+
// expected-note@+1{{consider declaring an isolated method on 'BankAccount' to perform the mutation}}
237+
a.owner = "cat"
236238
// expected-error@+1{{expression is 'async' but is not marked with 'await'}} {{7-7=await }} expected-note@+1{{property access is 'async'}}
237239
_ = b.owner
238240
_ = await b.owner == "cat"
@@ -279,7 +281,9 @@ func blender(_ peeler : () -> Void) {
279281
var money = await dollarsInBananaStand
280282
money -= 1200
281283

282-
dollarsInBananaStand = money // expected-error{{global actor 'BananaActor'-isolated var 'dollarsInBananaStand' can not be mutated from global actor 'OrangeActor'}}
284+
// expected-error@+2{{global actor 'BananaActor'-isolated var 'dollarsInBananaStand' can not be mutated from global actor 'OrangeActor'}}
285+
// expected-note@+1{{consider declaring an isolated method on 'BananaActor' to perform the mutation}}
286+
dollarsInBananaStand = money
283287

284288
// FIXME: these two errors seem a bit redundant.
285289
// expected-error@+2 {{actor-isolated var 'dollarsInBananaStand' cannot be passed 'inout' to implicitly 'async' function call}}

test/Concurrency/actor_existentials.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ func from_isolated_existential2(_ x: isolated any P) async {
3131
func from_nonisolated(_ x: any P) async {
3232
await x.f()
3333
x.prop += 1 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
34+
// expected-note@-1 {{consider declaring an isolated method on 'P' to perform the mutation}}
3435
x.prop = 100 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
36+
// expected-note@-1 {{consider declaring an isolated method on 'P' to perform the mutation}}
3537
}
3638

3739
func from_concrete(_ x: A) async {
3840
x.prop += 1 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
41+
// expected-note@-1 {{consider declaring an isolated method on 'A' to perform the mutation}}
3942
x.prop = 100 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
43+
// expected-note@-1 {{consider declaring an isolated method on 'A' to perform the mutation}}
4044
}
4145

4246
func from_isolated_concrete(_ x: isolated A) async {
@@ -52,8 +56,9 @@ actor Act {
5256
nonisolated let act = Act()
5357

5458
func bad() async {
55-
// expected-warning@+2 {{no 'async' operations occur within 'await' expression}}
56-
// expected-error@+1 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
59+
// expected-warning@+3 {{no 'async' operations occur within 'await' expression}}
60+
// expected-error@+2 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
61+
// expected-note@+1 {{consider declaring an isolated method on 'Act' to perform the mutation}}
5762
await act.i = 666
5863
}
5964

@@ -63,12 +68,14 @@ protocol Proto: Actor {
6368
extension Act: Proto {}
6469

6570
func good() async {
66-
// expected-warning@+2 {{no 'async' operations occur within 'await' expression}}
67-
// expected-error@+1 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
71+
// expected-warning@+3 {{no 'async' operations occur within 'await' expression}}
72+
// expected-error@+2 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
73+
// expected-note@+1 {{consider declaring an isolated method on 'Proto' to perform the mutation}}
6874
await (act as any Proto).i = 42
6975
let aIndirect: any Proto = act
7076

71-
// expected-warning@+2 {{no 'async' operations occur within 'await' expression}}
72-
// expected-error@+1 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
77+
// expected-warning@+3 {{no 'async' operations occur within 'await' expression}}
78+
// expected-error@+2 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
79+
// expected-note@+1 {{consider declaring an isolated method on 'Proto' to perform the mutation}}
7380
await aIndirect.i = 777
7481
}

test/Concurrency/actor_isolation.swift

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,23 @@ func checkAsyncPropertyAccess() async {
9898
let act = MyActor()
9999
let _ : Int = await act.mutable + act.mutable
100100
act.mutable += 1 // expected-error {{actor-isolated property 'mutable' can not be mutated from a nonisolated context}}
101+
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
101102

102103
act.superState += 1 // expected-error {{actor-isolated property 'superState' can not be mutated from a nonisolated context}}
104+
// expected-note@-1{{consider declaring an isolated method on 'MySuperActor' to perform the mutation}}
103105

104106
act.text[0].append("hello") // expected-error{{actor-isolated property 'text' can not be mutated from a nonisolated context}}
107+
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
105108

106109
// this is not the same as the above, because Array is a value type
107110
var arr = await act.text
108111
arr[0].append("hello")
109112

110113
act.text.append("no") // expected-error{{actor-isolated property 'text' can not be mutated from a nonisolated context}}
114+
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
111115

112116
act.text[0] += "hello" // expected-error{{actor-isolated property 'text' can not be mutated from a nonisolated context}}
117+
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
113118

114119
_ = act.point // expected-warning{{non-sendable type 'Point' of property 'point' cannot exit actor-isolated context}}
115120
// expected-warning@-1 {{expression is 'async' but is not marked with 'await'}}
@@ -269,9 +274,11 @@ extension MyActor {
269274
// expected-note@-1{{property access is 'async'}}
270275
_ = await otherActor.mutable
271276
otherActor.mutable = 0 // expected-error{{actor-isolated property 'mutable' can not be mutated on a nonisolated actor instance}}
277+
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
272278
acceptInout(&otherActor.mutable) // expected-error{{actor-isolated property 'mutable' can not be used 'inout' on a nonisolated actor instance}}
273-
// expected-error@+2{{actor-isolated property 'mutable' can not be mutated on a nonisolated actor instance}}
274-
// expected-warning@+1{{no 'async' operations occur within 'await' expression}}
279+
// expected-error@+3{{actor-isolated property 'mutable' can not be mutated on a nonisolated actor instance}}
280+
// expected-warning@+2{{no 'async' operations occur within 'await' expression}}
281+
// expected-note@+1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}
275282
await otherActor.mutable = 0
276283

277284
_ = otherActor.synchronous()
@@ -443,8 +450,10 @@ actor Crystal {
443450
_ = await globActorVar + globActorProp
444451

445452
globActorProp = 20 // expected-error {{global actor 'SomeGlobalActor'-isolated property 'globActorProp' can not be mutated on a different actor instance}}
453+
// expected-note@-1{{consider declaring an isolated method on 'SomeGlobalActor' to perform the mutation}}
446454

447455
globActorVar = 30 // expected-error {{global actor 'SomeGlobalActor'-isolated property 'globActorVar' can not be mutated on a different actor instance}}
456+
// expected-note@-1{{consider declaring an isolated method on 'SomeGlobalActor' to perform the mutation}}
448457

449458
// expected-error@+2 {{global actor 'SomeGlobalActor'-isolated property 'globActorVar' can not be used 'inout' on a different actor instance}}
450459
// expected-error@+1 {{actor-isolated property 'globActorVar' cannot be passed 'inout' to implicitly 'async' function call}}
@@ -864,7 +873,7 @@ extension SomeClassInActor.ID {
864873
@available(SwiftStdlib 5.1, *)
865874
actor SomeActorWithInits {
866875
// expected-note@+2 2 {{property declared here}}
867-
// expected-note@+1 3 {{mutation of this property is only permitted within the actor}}
876+
// expected-note@+1 4 {{mutation of this property is only permitted within the actor}}
868877
var mutableState: Int = 17
869878
var otherMutableState: Int
870879
// expected-note@+1 {{mutation of this property is only permitted within the actor}}
@@ -896,6 +905,14 @@ actor SomeActorWithInits {
896905
}
897906
local()
898907

908+
func localAsync() async {
909+
isolated() // expected-warning{{actor-isolated instance method 'isolated()' can not be referenced from a nonisolated context; this is an error in the Swift 6 language mode}}
910+
mutableState += 1 // expected-warning{{actor-isolated property 'mutableState' can not be mutated from a nonisolated context; this is an error in the Swift 6 language mode}}
911+
// expected-note@-1{{consider declaring an isolated method on 'SomeActorWithInits' to perform the mutation}}
912+
nonisolated()
913+
}
914+
Task { await localAsync() }
915+
899916
let _ = {
900917
defer {
901918
isolated() // expected-warning{{actor-isolated instance method 'isolated()' can not be referenced from a nonisolated context; this is an error in the Swift 6 language mode}}
@@ -990,8 +1007,7 @@ actor SomeActorWithInits {
9901007
}()
9911008
}
9921009

993-
994-
func isolated() { } // expected-note 9 {{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
1010+
func isolated() { } // expected-note 10 {{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
9951011
nonisolated func nonisolated() {}
9961012
}
9971013

@@ -1346,6 +1362,7 @@ actor Butterfly {
13461362
nonisolated init(cookies: Void) async {
13471363
self.init()
13481364
self.flapsPerSec += 1 // expected-error {{actor-isolated property 'flapsPerSec' can not be mutated from a nonisolated context}}
1365+
// expected-note@-1 {{consider declaring an isolated method on 'Butterfly' to perform the mutation}}
13491366
}
13501367

13511368
init(brownies: Void) {

test/Concurrency/toplevel/async-5-top-level.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func nonIsolatedAsync() async {
2525
// expected-warning@-1:5 {{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
2626
// expected-warning@-2:9 {{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
2727
// expected-note@-3:9 {{property access is 'async'}}
28+
// expected-note@-4 {{consider declaring an isolated method on 'MainActor' to perform the mutation}}
2829
}
2930

3031
@MainActor

test/Concurrency/toplevel/async-6-top-level.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func nonIsolatedAsync() async {
2424
// expected-error@-1:5 {{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
2525
// expected-error@-2:9 {{expression is 'async' but is not marked with 'await'}}
2626
// expected-note@-3:9 {{property access is 'async'}}
27+
// expected-note@-4 {{consider declaring an isolated method on 'MainActor' to perform the mutation}}
2728
}
2829

2930
@MainActor

test/Concurrency/toplevel/no-async-6-top-level.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func nonIsolatedAsync() async {
2626
a = a + 10 // expected-error{{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
2727
// expected-note@-1{{property access is 'async'}}
2828
// expected-error@-2{{expression is 'async' but is not marked with 'await'}}
29+
// expected-note@-3{{consider declaring an isolated method on 'MainActor' to perform the mutation}}
2930
}
3031

3132
@MainActor

0 commit comments

Comments
 (0)