Skip to content

[Concurrency] Suggest adding a method, when mutating actor property cross isolation #75922

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions include/swift/AST/ActorIsolation.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ class ActorIsolation {

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

ActorIsolation(Kind kind, Type globalActor)
: globalActor(globalActor), kind(kind), isolatedByPreconcurrency(false),
silParsed(false), parameterIndex(0) {}
ActorIsolation(Kind kind, Type globalActor);

public:
// No-argument constructor needed for DenseMap use in PostfixCompletion.cpp
Expand Down Expand Up @@ -203,7 +201,6 @@ class ActorIsolation {
}

NominalTypeDecl *getActor() const;
NominalTypeDecl *getActorOrNullPtr() const;

VarDecl *getActorInstance() const;

Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5455,6 +5455,9 @@ ERROR(distributed_actor_isolated_non_self_reference,none,
"distributed actor-isolated %kind0 can not be accessed from a "
"nonisolated context",
(const ValueDecl *))
NOTE(note_consider_method_for_isolated_property_mutation,none,
"consider declaring an isolated method on %0 to perform the mutation",
(const NominalTypeDecl *))
ERROR(conflicting_stored_property_isolation,none,
"%select{default|memberwise}0 initializer for %1 cannot be both %2 and %3",
(bool, Type, ActorIsolation, ActorIsolation))
Expand Down
45 changes: 15 additions & 30 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11651,6 +11651,10 @@ ActorIsolation::ActorIsolation(Kind kind, Expr *actor,
: actorInstance(actor), kind(kind), isolatedByPreconcurrency(false),
silParsed(false), parameterIndex(parameterIndex) {}

ActorIsolation::ActorIsolation(Kind kind, Type globalActor)
: globalActor(globalActor), kind(kind), isolatedByPreconcurrency(false),
silParsed(false), parameterIndex(0) {}

ActorIsolation
ActorIsolation::forActorInstanceParameter(Expr *actor,
unsigned parameterIndex) {
Expand Down Expand Up @@ -11701,51 +11705,29 @@ ActorIsolation ActorIsolation::forActorInstanceSelf(NominalTypeDecl *selfDecl) {
}

NominalTypeDecl *ActorIsolation::getActor() const {
assert(getKind() == ActorInstance ||
getKind() == GlobalActor);
assert(getKind() == ActorInstance || getKind() == GlobalActor);

if (silParsed)
return nullptr;

Type actorType;

if (auto *instance = actorInstance.dyn_cast<VarDecl *>()) {
actorType = instance->getTypeInContext();
} else if (auto *instance = actorInstance.dyn_cast<Expr *>()) {
actorType = instance->getType();
}

if (actorType) {
if (auto wrapped = actorType->getOptionalObjectType()) {
actorType = wrapped;
}
return actorType
->getReferenceStorageReferent()->getAnyActor();
if (getKind() == GlobalActor) {
return getGlobalActor()->getAnyNominal();
Copy link
Contributor Author

@ktoso ktoso Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActorIsolation::getActor() used to return garbage if it contained a GlobalActor before this fix.

We initialize the globalActor property but fall through to returning the actorInstance.get<NominalTypeDecl *>(); which is just some uninitialized memory for the global actor case.

This is fixed now, we properly handle both kinds of isolations the method claimed to support.

Just FYI @hborla

}

return actorInstance.get<NominalTypeDecl *>();
}

NominalTypeDecl *ActorIsolation::getActorOrNullPtr() const {
Copy link
Contributor Author

@ktoso ktoso Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You added this one recently but it seems to be a copy paste of the other and it is not used so I removed it @gottesmm if we should keep it let me know.

if (getKind() != ActorInstance || getKind() != GlobalActor)
return nullptr;

if (silParsed)
return nullptr;

Type actorType;

if (auto *instance = actorInstance.dyn_cast<VarDecl *>()) {
actorType = instance->getTypeInContext();
} else if (auto *instance = actorInstance.dyn_cast<Expr *>()) {
actorType = instance->getType();
} else if (auto *expr = actorInstance.dyn_cast<Expr *>()) {
actorType = expr->getType();
}

if (actorType) {
if (auto wrapped = actorType->getOptionalObjectType()) {
actorType = wrapped;
}
return actorType->getReferenceStorageReferent()->getAnyActor();
return actorType
->getReferenceStorageReferent()->getAnyActor();
}

return actorInstance.get<NominalTypeDecl *>();
Expand Down Expand Up @@ -11785,7 +11767,10 @@ bool ActorIsolation::isDistributedActor() const {
if (silParsed)
return false;

return getKind() == ActorInstance && getActor()->isDistributedActor();
if (getKind() != ActorInstance)
return false;

return getActor()->isDistributedActor();
}

bool ActorIsolation::isEqual(const ActorIsolation &lhs,
Expand Down
57 changes: 55 additions & 2 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1823,6 +1823,54 @@ static bool wasLegacyEscapingUseRestriction(AbstractFunctionDecl *fn) {
return !(fn->hasAsync()); // basic case: not async = had restriction.
}

/// Note that while a direct access to the actor isolated property is not legal
/// you may want to consider introducing an accessing method for a mutation.
static void
maybeNoteMutatingMethodSuggestion(ASTContext &C,
ValueDecl const *member,
SourceLoc memberLoc,
DeclContext const *refCxt,
ActorIsolation isolation,
std::optional<VarRefUseEnv> useKind) {
if (!member || !isa<VarDecl>(member))
return; // we only offer the note property mutations

if (!(isolation.getKind() == ActorIsolation::Kind::ActorInstance ||
isolation.getKind() == ActorIsolation::Kind::GlobalActor)) {
return;
}

if (useKind != VarRefUseEnv::Mutating) {
// This note is tailored for the 'mutating' access, i.e. when
// attempting to mutate a property, they should instead make an actor method to perform the mutation. Reading properties does not have the same restriction.
return;
}

if (!refCxt->isAsyncContext()) {
// don't suggest creating method when in sync context, as we won't be able
// to invoke it anyway so this would not be helpful to suggest
return;
}

if (auto actor = isolation.getActor()) {
NominalTypeDecl *actorTy =
actor;
// (isolation.getKind() == swift::ActorIsolation::ActorInstance ?
//)

C.Diags.diagnose(
memberLoc,
diag::note_consider_method_for_isolated_property_mutation,
actor);
// } else if (isolation.getKind() == swift::ActorIsolation::GlobalActor) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the commented out code here and above meant to be left in? NominalTypeDecl *actorTy is also unused

// C.Diags.diagnose(
// memberLoc,
// diag::note_consider_method_for_global_actor_isolated_property_mutation,
// isolation.getGlobalActor());
// }
}
}

/// Note that the given actor member is isolated.
static void noteIsolatedActorMember(ValueDecl const *decl,
std::optional<VarRefUseEnv> useKind) {
Expand Down Expand Up @@ -1944,20 +1992,23 @@ static bool memberAccessHasSpecialPermissionInSwift5(
}

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

auto isolation = getActorIsolation(const_cast<ValueDecl *>(member));
diags.diagnose(
memberLoc, diag::actor_isolated_non_self_reference,
member,
useKindInt,
baseActor.kind + 1,
baseActor.globalActor,
getActorIsolation(const_cast<ValueDecl *>(member)))
isolation)
.warnUntilSwiftVersion(6);

noteIsolatedActorMember(member, useKind);
maybeNoteMutatingMethodSuggestion(C, member, memberLoc, refCxt, isolation, useKind);
return true;
}

Expand Down Expand Up @@ -4438,6 +4489,8 @@ namespace {
refKind + 1, refGlobalActor,
result.isolation)
.warnUntilSwiftVersionIf(preconcurrencyContext, 6);
maybeNoteMutatingMethodSuggestion(ctx, decl, loc, getDeclContext(), result.isolation,
kindOfUsage(decl, context).value_or(VarRefUseEnv::Read));

if (derivedConformanceType) {
auto *decl = dyn_cast<ValueDecl>(getDeclContext()->getAsDecl());
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/Inputs/GlobalVariables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ public struct Globals {
public static let integerConstant = 0
public static var integerMutable = 0

public static nonisolated(unsafe) let nonisolatedUnsafeIntegerConstant = 0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this caused an un-necessary warning we're not testing for; removed the warning

public static let nonisolatedUnsafeIntegerConstant = 0
public static nonisolated(unsafe) var nonisolatedUnsafeIntegerMutable = 0

@MainActor public static var actorInteger = 0
Expand Down
8 changes: 6 additions & 2 deletions test/Concurrency/actor_call_implicitly_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ func anotherAsyncFunc() async {

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

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

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

// FIXME: these two errors seem a bit redundant.
// expected-error@+2 {{actor-isolated var 'dollarsInBananaStand' cannot be passed 'inout' to implicitly 'async' function call}}
Expand Down
19 changes: 13 additions & 6 deletions test/Concurrency/actor_existentials.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ func from_isolated_existential2(_ x: isolated any P) async {
func from_nonisolated(_ x: any P) async {
await x.f()
x.prop += 1 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
// expected-note@-1 {{consider declaring an isolated method on 'P' to perform the mutation}}
x.prop = 100 // expected-error {{actor-isolated property 'prop' can not be mutated from a nonisolated context}}
// expected-note@-1 {{consider declaring an isolated method on 'P' to perform the mutation}}
}

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

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

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

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

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

// expected-warning@+2 {{no 'async' operations occur within 'await' expression}}
// expected-error@+1 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
// expected-warning@+3 {{no 'async' operations occur within 'await' expression}}
// expected-error@+2 {{actor-isolated property 'i' can not be mutated from a nonisolated context}}
// expected-note@+1 {{consider declaring an isolated method on 'Proto' to perform the mutation}}
await aIndirect.i = 777
}
27 changes: 22 additions & 5 deletions test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,23 @@ func checkAsyncPropertyAccess() async {
let act = MyActor()
let _ : Int = await act.mutable + act.mutable
act.mutable += 1 // expected-error {{actor-isolated property 'mutable' can not be mutated from a nonisolated context}}
// expected-note@-1{{consider declaring an isolated method on 'MyActor' to perform the mutation}}

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

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

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

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

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

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

_ = otherActor.synchronous()
Expand Down Expand Up @@ -443,8 +450,10 @@ actor Crystal {
_ = await globActorVar + globActorProp

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

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

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

func localAsync() async {
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}}
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}}
// expected-note@-1{{consider declaring an isolated method on 'SomeActorWithInits' to perform the mutation}}
nonisolated()
}
Task { await localAsync() }

let _ = {
defer {
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}}
Expand Down Expand Up @@ -990,8 +1007,7 @@ actor SomeActorWithInits {
}()
}


func isolated() { } // expected-note 9 {{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
func isolated() { } // expected-note 10 {{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
nonisolated func nonisolated() {}
}

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

init(brownies: Void) {
Expand Down
1 change: 1 addition & 0 deletions test/Concurrency/toplevel/async-5-top-level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func nonIsolatedAsync() async {
// expected-warning@-1:5 {{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
// expected-warning@-2:9 {{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
// expected-note@-3:9 {{property access is 'async'}}
// expected-note@-4 {{consider declaring an isolated method on 'MainActor' to perform the mutation}}
}

@MainActor
Expand Down
1 change: 1 addition & 0 deletions test/Concurrency/toplevel/async-6-top-level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func nonIsolatedAsync() async {
// expected-error@-1:5 {{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
// expected-error@-2:9 {{expression is 'async' but is not marked with 'await'}}
// expected-note@-3:9 {{property access is 'async'}}
// expected-note@-4 {{consider declaring an isolated method on 'MainActor' to perform the mutation}}
}

@MainActor
Expand Down
1 change: 1 addition & 0 deletions test/Concurrency/toplevel/no-async-6-top-level.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func nonIsolatedAsync() async {
a = a + 10 // expected-error{{main actor-isolated var 'a' can not be mutated from a nonisolated context}}
// expected-note@-1{{property access is 'async'}}
// expected-error@-2{{expression is 'async' but is not marked with 'await'}}
// expected-note@-3{{consider declaring an isolated method on 'MainActor' to perform the mutation}}
}

@MainActor
Expand Down