Skip to content

[Concurrency] Ensure we do implicit-async from @actorIndependent & global-actors contexts #36142

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
Feb 25, 2021
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
47 changes: 43 additions & 4 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,21 @@ namespace {
return ActorIsolation::forIndependent(ActorIndependentKind::Safe);
}

bool isInAsynchronousContext() const {
auto dc = getDeclContext();
if (auto func = dyn_cast<AbstractFunctionDecl>(dc))
return func->isAsyncContext();

if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
if (auto type = closure->getType()) {
if (auto fnType = type->getAs<AnyFunctionType>())
return fnType->isAsync();
}
}

return false;
}

/// Check a reference to an entity within a global actor.
bool checkGlobalActorReference(
ConcreteDeclRef valueRef, SourceLoc loc, Type globalActor,
Expand All @@ -1403,6 +1418,9 @@ namespace {
/// Returns true if this global actor reference is the callee of an Apply.
/// NOTE: This check mutates the identified ApplyExpr if it returns true!
auto inspectForImplicitlyAsync = [&] () -> bool {
// If our current context isn't an asynchronous one, don't
if (!isInAsynchronousContext())
return false;

// Is this global actor reference outside of an ApplyExpr?
if (applyStack.size() == 0)
Expand Down Expand Up @@ -1727,10 +1745,13 @@ namespace {
}

case ActorIsolationRestriction::ActorSelf: {
// Must reference actor-isolated state on 'self'.
auto *selfVar = getReferencedSelf(base);
if (!selfVar) {
// actor-isolated non-self calls are implicitly async and thus OK.
// Local function to check for implicit async promotion.
auto checkImplicitlyAsync = [&]() -> Optional<bool> {
if (!isInAsynchronousContext())
return None;

// actor-isolated non-isolated-self calls are implicitly async
// and thus OK.
if (maybeImplicitAsync && isa<AbstractFunctionDecl>(member)) {
markNearestCallAsImplicitlyAsync();

Expand All @@ -1740,6 +1761,16 @@ namespace {
ConcurrentReferenceKind::SynchronousAsAsyncCall);
}

return None;
};

// Must reference actor-isolated state on 'self'.
auto *selfVar = getReferencedSelf(base);
if (!selfVar) {
// Check for implicit async.
if (auto result = checkImplicitlyAsync())
return *result;

ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_non_self_reference,
member->getDescriptiveKind(),
Expand Down Expand Up @@ -1772,6 +1803,10 @@ namespace {
return false;

case ActorIsolation::Independent: {
// Check for implicit async.
if (auto result = checkImplicitlyAsync())
return *result;

// The 'self' is for an actor-independent member, which means
// we cannot refer to actor-isolated state.
auto diag = findActorIndependentReason(curDC);
Expand All @@ -1782,6 +1817,10 @@ namespace {
}

case ActorIsolation::GlobalActor:
// Check for implicit async.
if (auto result = checkImplicitlyAsync())
return *result;

// The 'self' is for a member that's part of a global actor, which
// means we cannot refer to actor-isolated state.
ctx.Diags.diagnose(
Expand Down
6 changes: 2 additions & 4 deletions test/Concurrency/actor_call_implicitly_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ actor BankAccount {
// expected-note@+1 {{calls to instance method 'balance()' from outside of its actor context are implicitly asynchronous}}
func balance() -> Int { return curBalance }

// expected-note@+1 {{calls to instance method 'deposit' from outside of its actor context are implicitly asynchronous}}
// expected-note@+1 2{{calls to instance method 'deposit' from outside of its actor context are implicitly asynchronous}}
func deposit(_ amount : Int) -> Int {
guard amount >= 0 else { return 0 }

Expand Down Expand Up @@ -125,14 +125,12 @@ func anotherAsyncFunc() async {

}

// expected-note@+2 {{add 'async' to function 'regularFunc()' to make it asynchronous}} {{19-19= async}}
// expected-note@+1 {{add '@asyncHandler' to function 'regularFunc()' to create an implicit asynchronous context}} {{1-1=@asyncHandler }}
func regularFunc() {
let a = BankAccount(initialDeposit: 34)

_ = a.deposit //expected-error{{actor-isolated instance method 'deposit' can only be referenced inside the actor}}

_ = a.deposit(1) // expected-error{{'async' in a function that does not support concurrency}}
_ = a.deposit(1) // expected-error{{actor-isolated instance method 'deposit' can only be referenced inside the actor}}
}


Expand Down
32 changes: 17 additions & 15 deletions test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func acceptEscapingAsyncClosure<T>(_: @escaping () async -> T) { }
actor MySuperActor {
var superState: Int = 25 // expected-note {{mutable state is only available within the actor instance}}

func superMethod() { } // expected-note 3 {{calls to instance method 'superMethod()' from outside of its actor context are implicitly asynchronous}}
func superMethod() { } // expected-note 2 {{calls to instance method 'superMethod()' from outside of its actor context are implicitly asynchronous}}
func superAsyncMethod() async { }

subscript (index: Int) -> String { // expected-note 3{{subscript declared here}}
Expand All @@ -35,7 +35,7 @@ actor MyActor: MySuperActor {
class func synchronousClass() { }
static func synchronousStatic() { }

func synchronous() -> String { text.first ?? "nothing" } // expected-note 21{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
func synchronous() -> String { text.first ?? "nothing" } // expected-note 20{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
func asynchronous() async -> String { synchronous() }
}

Expand All @@ -45,7 +45,6 @@ extension MyActor {
set { }
}

// expected-note@+1 {{add 'async' to function 'actorIndependentFunc(otherActor:)' to make it asynchronous}} {{67-67= async}}
@actorIndependent func actorIndependentFunc(otherActor: MyActor) -> Int {
_ = immutable
_ = text[0] // expected-error{{actor-isolated property 'text' can not be referenced from an '@actorIndependent' context}}
Expand All @@ -65,8 +64,11 @@ extension MyActor {
_ = otherActor.actorIndependentVar
otherActor.actorIndependentVar = 17

// async promotion
_ = synchronous() // expected-error{{actor-isolated instance method 'synchronous()' can not be referenced from an '@actorIndependent' context}}

// Global actors
syncGlobalActorFunc() /// expected-error{{'async' in a function that does not support concurrency}}
syncGlobalActorFunc() /// expected-error{{global function 'syncGlobalActorFunc()' isolated to global actor 'SomeGlobalActor' can not be referenced from an '@actorIndependent' context}}
_ = syncGlobalActorFunc // expected-error{{global function 'syncGlobalActorFunc()' isolated to global actor 'SomeGlobalActor' can not be referenced from an '@actorIndependent' context}}

// Global data is okay if it is immutable.
Expand Down Expand Up @@ -232,7 +234,7 @@ struct GenericGlobalActor<T> {
static var shared: SomeActor { SomeActor() }
}

@SomeGlobalActor func syncGlobalActorFunc() { syncGlobalActorFunc() } // expected-note{{calls to global function 'syncGlobalActorFunc()' from outside of its actor context are implicitly asynchronous}}
@SomeGlobalActor func syncGlobalActorFunc() { syncGlobalActorFunc() } // expected-note 2{{calls to global function 'syncGlobalActorFunc()' from outside of its actor context are implicitly asynchronous}}
@SomeGlobalActor func asyncGlobalActorFunc() async { await asyncGlobalActorFunc() }

@SomeOtherGlobalActor func syncOtherGlobalActorFunc() { }
Expand Down Expand Up @@ -260,20 +262,24 @@ extension MyActor {
await asyncOtherGlobalActorFunc()

_ = immutable
_ = synchronous() // expected-error{{actor-isolated instance method 'synchronous()' can not be referenced from context of global actor 'SomeGlobalActor'}}
_ = synchronous() // expected-error{{call is 'async' but is not marked with 'await'}}
_ = await synchronous()
_ = text[0] // expected-error{{actor-isolated property 'text' can not be referenced from context of global actor 'SomeGlobalActor'}}

// Accesses on 'self' are only okay for immutable and asynchronous, because
// we are outside of the actor instance.
_ = self.immutable
_ = self.synchronous() // expected-error{{actor-isolated instance method 'synchronous()' can not be referenced from context of global actor 'SomeGlobalActor'}}
_ = self.synchronous() // expected-error{{call is 'async' but is not marked with 'await'}}
_ = await self.synchronous()

_ = await self.asynchronous()
_ = self.text[0] // expected-error{{actor-isolated property 'text' can not be referenced from context of global actor 'SomeGlobalActor'}}
_ = self[0] // expected-error{{actor-isolated subscript 'subscript(_:)' can not be referenced from context of global actor 'SomeGlobalActor'}}

// Accesses on 'super' are not okay; we're outside of the actor.
_ = super.superState // expected-error{{actor-isolated property 'superState' can not be referenced from context of global actor 'SomeGlobalActor'}}
super.superMethod() // expected-error{{actor-isolated instance method 'superMethod()' can not be referenced from context of global actor 'SomeGlobalActor'}}
super.superMethod() // expected-error{{call is 'async' but is not marked with 'await'}}
await super.superMethod()
await super.superAsyncMethod()
_ = super[0] // expected-error{{actor-isolated subscript 'subscript(_:)' can not be referenced from context of global actor 'SomeGlobalActor'}}

Expand All @@ -288,16 +294,14 @@ extension MyActor {
}

struct GenericStruct<T> {
@GenericGlobalActor<T> func f() { } // expected-note{{calls to instance method 'f()' from outside of its actor context are implicitly asynchronous}}
@GenericGlobalActor<T> func f() { } // expected-note 2{{calls to instance method 'f()' from outside of its actor context are implicitly asynchronous}}

@GenericGlobalActor<T> func g() {
f() // okay
}

// expected-note@+2 {{add '@asyncHandler' to function 'h()' to create an implicit asynchronous context}} {{3-3=@asyncHandler }}
// expected-note@+1 {{add 'async' to function 'h()' to make it asynchronous}} {{39-39= async}}
@GenericGlobalActor<String> func h() {
f() // expected-error{{'async' in a function that does not support concurrency}}
f() // expected-error{{instance method 'f()' isolated to global actor 'GenericGlobalActor<T>' can not be referenced from different global actor 'GenericGlobalActor<String>'}}
_ = f // expected-error{{instance method 'f()' isolated to global actor 'GenericGlobalActor<T>' can not be referenced from different global actor 'GenericGlobalActor<String>'}}
}
}
Expand Down Expand Up @@ -472,9 +476,7 @@ class SomeClassInActor {

extension SomeClassInActor.ID {
func f(_ object: SomeClassInActor) { // expected-note{{add '@MainActor' to make instance method 'f' part of global actor 'MainActor'}}
// expected-note@-1{{add 'async' to function 'f' to make it asynchronous}}
// expected-note@-2{{add '@asyncHandler' to function 'f' to create an implicit asynchronous context}}
object.inActor() // expected-error{{'async' in a function that does not support concurrency}}
object.inActor() // expected-error{{instance method 'inActor()' isolated to global actor 'MainActor' can not be referenced from this context}}
}
}

Expand Down
7 changes: 2 additions & 5 deletions test/Concurrency/async_let_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@ actor MyActor {
let immutable: Int = 17
var text: [String] = []

func synchronous() -> String { text.first ?? "nothing" } // expected-note 2 {{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
func synchronous() -> String { text.first ?? "nothing" }
func asynchronous() async -> String { synchronous() }

func testAsyncLetIsolation() async {
async let x = self.synchronous()
// expected-error @-1{{actor-isolated instance method 'synchronous()' cannot be referenced from 'async let' initializer}}

async let y = await self.asynchronous()

async let z = synchronous()
// expected-error @-1{{actor-isolated instance method 'synchronous()' cannot be referenced from 'async let' initializer}}

var localText = text
async let w = localText.removeLast()
// expected-error@-1{{mutation of captured var 'localText' in concurrently-executing code}}
async let w = localText.removeLast() // expected-error{{mutation of captured var 'localText' in concurrently-executing code}}

_ = await x
_ = await y
Expand Down
22 changes: 8 additions & 14 deletions test/Concurrency/global_actor_from_ordinary_context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,9 @@ func referenceAsyncGlobalActor() {
}


// expected-note@+3 {{add '@asyncHandler' to function 'callGlobalActor()' to create an implicit asynchronous context}} {{1-1=@asyncHandler }}
// expected-note@+2 {{add 'async' to function 'callGlobalActor()' to make it asynchronous}} {{23-23= async}}
// expected-note@+1 {{add '@SomeGlobalActor' to make global function 'callGlobalActor()' part of global actor 'SomeGlobalActor'}} {{1-1=@SomeGlobalActor }}
func callGlobalActor() {
syncGlobActorFn() // expected-error {{'async' in a function that does not support concurrency}}
syncGlobActorFn() // expected-error {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
}

func fromClosure() {
Expand All @@ -70,40 +68,36 @@ func fromClosure() {
x()
}()

// expected-error@+2 {{'async' in a function that does not support concurrency}}
// expected-error@+1 {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
let _ = { syncGlobActorFn() }()
}

class Taylor {
init() { // expected-note {{add 'async' to function 'init()' to make it asynchronous}} {{9-9= async}}
syncGlobActorFn() // expected-error {{'async' in a function that does not support concurrency}}
init() {
syncGlobActorFn() // expected-error {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}

// expected-error@+1 {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
_ = syncGlobActorFn
}

deinit {
syncGlobActorFn() // expected-error {{'async' in a function that does not support concurrency}}
syncGlobActorFn() // expected-error {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}

// expected-error@+1 {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
_ = syncGlobActorFn
}

// expected-note@+3 2 {{add '@SomeGlobalActor' to make instance method 'method1()' part of global actor 'SomeGlobalActor'}} {{3-3=@SomeGlobalActor }}
// expected-note@+2 {{add '@asyncHandler' to function 'method1()' to create an implicit asynchronous context}} {{3-3=@asyncHandler }}
// expected-note@+1 {{add 'async' to function 'method1()' to make it asynchronous}} {{17-17= async}}
// expected-note@+1 2 {{add '@SomeGlobalActor' to make instance method 'method1()' part of global actor 'SomeGlobalActor'}} {{3-3=@SomeGlobalActor }}
func method1() {
syncGlobActorFn() // expected-error {{'async' in a function that does not support concurrency}}
syncGlobActorFn() // expected-error {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}

// expected-error@+1 {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
_ = syncGlobActorFn
}

// expected-note@+2 2 {{add '@SomeGlobalActor' to make instance method 'cannotBeHandler()' part of global actor 'SomeGlobalActor'}} {{3-3=@SomeGlobalActor }}
// expected-note@+1 {{add 'async' to function 'cannotBeHandler()' to make it asynchronous}}
// expected-note@+1 2 {{add '@SomeGlobalActor' to make instance method 'cannotBeHandler()' part of global actor 'SomeGlobalActor'}} {{3-3=@SomeGlobalActor }}
func cannotBeHandler() -> Int {
syncGlobActorFn() // expected-error {{'async' in a function that does not support concurrency}}
syncGlobActorFn() // expected-error {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}

// expected-error@+1 {{global function 'syncGlobActorFn()' isolated to global actor 'SomeGlobalActor' can not be referenced from this context}}
_ = syncGlobActorFn
Expand Down
Loading