Files
swift-mirror/test/attr/execution_behavior_attrs.swift
Anthony Latsis dc1458762e Sema: Make @concurrent not imply async on closures
The present approach is not prudent because `@concurrent` synchronous
functions, a natural extension, are a likely-to-happen future direction,
whereas the current inference rule is entirely grounded on `@concurrent`
being exclusive to async functions.

If we were to ship this rule, we would have to keep the promise for
backwards compatibility when implementing the aforementioned future
direction, replacing one inconsistency with another, and possibly
introducing new bug-prone expression checking code.

```swift
func foo(_: () -> Void) {}
func foo(_: () async -> Void) {}

// In a future without this change and  `@concurrent` synchronous
// functions accepted, the first call resolves to the first overload,
// and the second call resolves to the second, despite `@concurrent` no
// longer implying `async`.
foo { }
foo { @concurrent in }
```

This change also drops the fix-it for removing `@concurrent` when used
on a synchronous closure. With the inference rule gone, and the
diagnosis delayed until after solution application, this error raises a
fairly balanced choice between removing the attribute and being
explicit about the effect, where a unilateral suggestion is quite
possibly more harmful than useful.

(cherry picked from commit 58d5059617)
2025-07-16 20:49:37 +01:00

226 lines
8.4 KiB
Swift

// RUN: %target-typecheck-verify-swift -target %target-swift-5.1-abi-triple
// REQUIRES: concurrency
// FIXME: Bad parser diagnostic on C++ side
nonisolated(something) func invalidAttr() async {} // expected-error {{cannot find 'nonisolated' in scope}}
// expected-error@-1 {{cannot find 'something' in scope}}
// expected-error@-2 {{consecutive statements on a line must be separated by ';'}}
@concurrent nonisolated(nonsending) func mutipleAttrs() async {}
// expected-error@-1 {{global function 'mutipleAttrs()' has multiple actor-isolation attributes (@concurrent and 'nonisolated(nonsending)')}}
do {
nonisolated(nonsending) struct S {}
// expected-error@-1 {{'nonisolated(nonsending)' is only applicable to asynchronous functions, initializers, subscripts and computed properties}}
func f(nonisolated(nonsending) param: Int) {}
// expected-error@-1 {{expected parameter name followed by ':'}}
// expected-error@-2 {{parameter may have at most one of the 'inout', 'borrowing', or 'consuming' specifiers}}
}
@concurrent func nonAsync1() {}
// expected-error@-1 {{cannot use @concurrent on non-async global function 'nonAsync1()'}}
nonisolated(nonsending) func nonAsync2() {}
// expected-error@-1 {{cannot use 'nonisolated(nonsending)' on non-async global function 'nonAsync2()'}}
@concurrent func testGlobal() async {} // Ok
struct Test {
@concurrent init() {}
// expected-error@-1 {{cannot use @concurrent on non-async initializer 'init()'}}
@concurrent init(async: Void) async {}
@concurrent func member() {}
// expected-error@-1 {{cannot use @concurrent on non-async instance method 'member()'}}
@concurrent func member() async {} // Ok
@concurrent var syncP: Int {
// expected-error@-1 {{cannot use @concurrent on non-async property 'syncP'}}
get {}
}
@concurrent var asyncP: Int {
get async {}
}
// expected-error@+1 {{cannot use 'nonisolated(nonsending)' on non-async subscript 'subscript(sync:)'}}
nonisolated(nonsending) subscript(sync _: Int) -> Bool {
@concurrent get { false }
// expected-error@-1 {{@concurrent' attribute cannot be applied to this declaration}}
@concurrent set { }
// expected-error@-1 {{@concurrent' attribute cannot be applied to this declaration}}
}
nonisolated(nonsending) subscript(async _: Int) -> Bool {
get async {}
}
// FIXME: Incorrect quotes due to inconsistent DeclAttribute printing between modifiers and attributes
nonisolated(nonsending) var storedVar: Int
// expected-error@-1 {{''nonisolated(nonsending)'' must not be used on stored properties}}
nonisolated(nonsending) let storedLet: Int
// expected-error@-1 {{''nonisolated(nonsending)'' must not be used on stored properties}}
}
do {
class C {
nonisolated(nonsending) deinit {}
// expected-error@-1 {{'nonisolated(nonsending)' is only applicable to asynchronous functions, initializers, subscripts and computed properties}}
}
}
do {
nonisolated(nonsending) func local() async {} // Ok
protocol P {
nonisolated(nonsending) var syncP: Int { get }
// expected-error@-1 {{cannot use 'nonisolated(nonsending)' on non-async property 'syncP'}}
nonisolated(nonsending) var asyncP: Int { get async }
}
}
struct TestAttributeCollisions {
@concurrent nonisolated func testNonIsolated() async {}
@concurrent func test(arg: isolated MainActor) async {}
// expected-error@-1 {{cannot use @concurrent on instance method 'test(arg:)' because it has an isolated parameter: 'arg'}}
@concurrent subscript(test arg: isolated MainActor) -> Int {
// expected-error@-1 {{cannot use @concurrent on subscript 'subscript(test:)' because it has an isolated parameter: 'arg'}}
get async {}
}
@concurrent func testIsolationAny(arg: @isolated(any) () -> Void) async {}
@concurrent subscript(testIsolationAny arg: @isolated(any) () -> Void) -> Int {
get async {}
}
@MainActor @concurrent func testGlobalActor() async {}
// expected-error@-1 {{instance method 'testGlobalActor()' has multiple actor-isolation attributes (@MainActor and @concurrent)}}
nonisolated(nonsending) nonisolated func testNonIsolatedCaller() async {} // expected-error {{duplicate modifier}} expected-note {{modifier already specified here}}
@MainActor nonisolated(nonsending) func testGlobalActorCaller() async {}
// expected-error@-1 {{instance method 'testGlobalActorCaller()' has multiple actor-isolation attributes (@MainActor and 'nonisolated(nonsending)')}}
nonisolated(nonsending) func testCaller(arg: isolated MainActor) async {}
// expected-error@-1 {{cannot use 'nonisolated(nonsending)' on instance method 'testCaller(arg:)' because it has an isolated parameter: 'arg'}}
@concurrent @Sendable func test(_: @Sendable () -> Void, _: sending Int) async {} // Ok
@Sendable nonisolated(nonsending) func testWithSendableCaller(_: @Sendable () -> Void, _: sending Int) async {} // Ok
}
@MainActor
protocol P {
func test() async
}
struct InfersMainActor : P {
@concurrent func test() async {}
}
@MainActor
struct IsolatedType {
@concurrent func test() async {}
}
// `@concurrent` on closures does not contribute to `async` inference.
do {
_ = { @concurrent in }
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
_ = { @concurrent () -> Int in }
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
// Explicit effect precludes effects inference from the body.
_ = { @concurrent () throws in await awaitMe() }
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
// expected-error@-2:40 {{'async' call in a function that does not support concurrency}}{{none}}
_ = { @concurrent () async in }
_ = { @concurrent in await awaitMe() }
func awaitMe() async {}
do {
func foo(_: () -> Void) {}
func foo(_: () async -> Void) async {}
func sync() {
foo { @concurrent in }
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
}
// expected-note@+1:10 {{add 'async' to function 'sync2()' to make it asynchronous}}{{17-17= async}}
func sync2() {
foo { @concurrent in await awaitMe() }
// expected-error@-1:7 {{'async' call in a function that does not support concurrency}}{{none}}
}
func async() async {
// Even though the context is async, the sync overload is favored because
// the closure is sync, so the error is expected.
foo { @concurrent in }
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
foo { @concurrent in await awaitMe() }
// expected-error@-1:7 {{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
// expected-note@-2:7 {{call is 'async'}}{{none}}
}
}
do {
func foo(_: (Int) async -> Void) {}
func foo(_: (Int) -> Void) async {}
func sync() {
// OK, sync overload picked.
foo { @concurrent _ in }
}
func async() async {
foo { @concurrent _ in }
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
// expected-error@-2:7 {{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
// expected-note@-3:7 {{call is 'async'}}{{none}}
}
}
do {
func foo<T>(_: T) {}
foo({@concurrent in })
// expected-error@-1:10 {{cannot use @concurrent on non-async closure}}{{none}}
}
}
_ = { @MainActor @concurrent in
// expected-error@-1 {{cannot use @concurrent because function type is isolated to a global actor 'MainActor'}}
}
// Make sure that explicit use of `@concurrent` doesn't interfere with inference of `throws` from the body.
do {
func acceptsThrowing(_ body: () async throws -> Void) async {
}
struct Invocation {
func throwingFn() async throws {
}
}
func test(invocation: Invocation) async {
await acceptsThrowing({ @concurrent in try await invocation.throwingFn() }) // Ok
await acceptsThrowing({ @concurrent [invocation] in try await invocation.throwingFn() }) // Ok
await acceptsThrowing({ @concurrent in // Ok
_ = 42
try await invocation.throwingFn()
})
}
}
do {
nonisolated(nonsending)
func testOnDecl(_: @isolated(any) () -> Void) async {} // Ok
func testOnType1(_: nonisolated(nonsending) @isolated(any) () async -> Void) {}
// expected-error@-1 {{cannot use 'nonisolated(nonsending)' together with '@isolated(any)'}}
func testOnType2(_: @concurrent @isolated(any) () async -> Void) {}
// expected-error@-1 {{cannot use '@concurrent' together with '@isolated(any)'}}
}