From 19b4c5c20cf00a38aac21b495c9fd37c24f2cb87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Fri, 18 Jun 2021 09:36:08 +0200 Subject: [PATCH 1/4] [SE-0296] Allow overloads that differ only in async --- proposals/0296-async-await.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/proposals/0296-async-await.md b/proposals/0296-async-await.md index ebdce833dd..a0f1bd40ef 100644 --- a/proposals/0296-async-await.md +++ b/proposals/0296-async-await.md @@ -471,7 +471,17 @@ These two functions have different names and signatures, even though they share doSomething() // problem: can call either, unmodified Swift rules prefer the `async` version ``` -Swift's overloading rules prefer to call a function with fewer default arguments, so the addition of the `async` function would break existing code that called the original `doSomething(completionHandler:)` with no completion handler. This would get an error along the lines of: +A similar problem exists for APIs that evolve into providing both a synchronous and an asynchronous version of the same function, with the same signature. Such pairs allow APIs to gracefully provide a new synchronous function which is able to better fit in the Swift asynchronous landscape, without breaking backward compatibility. New asynchronous functions can support, for example, cancellation (covered in the [Structured Concurrency](https://github.com/DougGregor/swift-evolution/blob/structured-concurrency/proposals/nnnn-structured-concurrency.md) proposal). + +```swift +// Existing synchronous API +func doSomethingElse() { ... } + +// New and enhanced asynchronous API +func doSomethingElse() async { ... } +``` + +In the first case, Swift's overloading rules prefer to call a function with fewer default arguments, so the addition of the `async` function would break existing code that called the original `doSomething(completionHandler:)` with no completion handler. This would get an error along the lines of: ``` error: `async` function cannot be called from non-asynchronous context @@ -479,16 +489,16 @@ error: `async` function cannot be called from non-asynchronous context This presents problems for code evolution, because developers of existing asynchronous libraries would have to either have a hard compatiblity break (e.g, to a new major version) or would need have different names for all of the new `async` versions. The latter would likely result in a scheme such as [C#'s pervasive `Async` suffix](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model). -Instead, we propose an overload-resolution rule to select the appropriate function based on the context of the call. Given a call, overload resolution prefers non-`async` functions within a synchronous context (because such contexts cannot contain a call to an `async` function). Furthermore, overload resolution prefers `async` functions within an asynchronous context (because such contexts should avoid stepping out of the asynchronous model into blocking APIs). When overload resolution selects an `async` function, that call is still subject to the rule that it must occur within an `await` expression. +The second case, where both functions have the same signature and only differ in `async`, is normally rejected by existing Swift's overloading rules. Those do not allow two functions to differ only in their *effects*, and one can not define two functions that only differ in `throws`, for example. -Note that we follow the design of `throws` in disallowing overloads that differ *only* in `async`: +``` +// error: redeclaration of function `doSomethingElse()`. +``` -```swift -func doSomething() -> String { /* ... */ } // synchronous, blocking -func doSomething() async -> String { /* ... */ } // asynchronous +This also presents a problem for code evolution, because developers of existing libraries just could not preserve their existing synchronous APIs, and support new asynchronous features. + +Instead, we propose an overload-resolution rule to select the appropriate function based on the context of the call. Given a call, overload resolution prefers non-`async` functions within a synchronous context (because such contexts cannot contain a call to an `async` function). Furthermore, overload resolution prefers `async` functions within an asynchronous context (because such contexts should avoid stepping out of the asynchronous model into blocking APIs). When overload resolution selects an `async` function, that call is still subject to the rule that it must occur within an `await` expression. -// error: redeclaration of function `doSomething()`. -``` ### Autoclosures From 182b281a0c730e784d2cea30a9899e57e0098eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Fri, 18 Jun 2021 09:43:04 +0200 Subject: [PATCH 2/4] Fix typo, and enhance wording --- proposals/0296-async-await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0296-async-await.md b/proposals/0296-async-await.md index a0f1bd40ef..2fe71bc7d7 100644 --- a/proposals/0296-async-await.md +++ b/proposals/0296-async-await.md @@ -471,7 +471,7 @@ These two functions have different names and signatures, even though they share doSomething() // problem: can call either, unmodified Swift rules prefer the `async` version ``` -A similar problem exists for APIs that evolve into providing both a synchronous and an asynchronous version of the same function, with the same signature. Such pairs allow APIs to gracefully provide a new synchronous function which is able to better fit in the Swift asynchronous landscape, without breaking backward compatibility. New asynchronous functions can support, for example, cancellation (covered in the [Structured Concurrency](https://github.com/DougGregor/swift-evolution/blob/structured-concurrency/proposals/nnnn-structured-concurrency.md) proposal). +A similar problem exists for APIs that evolve into providing both a synchronous and an asynchronous version of the same function, with the same signature. Such pairs allow APIs to provide a new asynchronous function which better fits in the Swift asynchronous landscape, without breaking backward compatibility. New asynchronous functions can support, for example, cancellation (covered in the [Structured Concurrency](https://github.com/DougGregor/swift-evolution/blob/structured-concurrency/proposals/nnnn-structured-concurrency.md) proposal). ```swift // Existing synchronous API From b83cd6c640920389e67e5c93c20caec16de07e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Wed, 23 Jun 2021 19:34:28 +0200 Subject: [PATCH 3/4] [SE-0296] Detail resolution of overloads that differ only in async --- proposals/0296-async-await.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/proposals/0296-async-await.md b/proposals/0296-async-await.md index 2fe71bc7d7..f8a57fa75b 100644 --- a/proposals/0296-async-await.md +++ b/proposals/0296-async-await.md @@ -499,6 +499,29 @@ This also presents a problem for code evolution, because developers of existing Instead, we propose an overload-resolution rule to select the appropriate function based on the context of the call. Given a call, overload resolution prefers non-`async` functions within a synchronous context (because such contexts cannot contain a call to an `async` function). Furthermore, overload resolution prefers `async` functions within an asynchronous context (because such contexts should avoid stepping out of the asynchronous model into blocking APIs). When overload resolution selects an `async` function, that call is still subject to the rule that it must occur within an `await` expression. +The overload-resolution rule depends on the synchronous or asynchronous context, in which the compiler selects one and only one overload. The selection of the async overload requires an `await` expression, as all introductions of a potential suspension point: + +```swift +func f() async { + // In an asynchronous context, the async overload is preferred: + await doSomething() + // Compiler error: Expression is 'async' but is not marked with 'await' + doSomething() +} +``` + +In non-`async` functions, and closures without any `await` expression, the compiler selects the non-`async` overload: + +``` +func f() async { + let f2 = { + // In a synchronous context, the non-async overload is preferred: + doSomething() + } + f2() +} +``` + ### Autoclosures From 93f0092fc46609a00c48c8436a2b56edd852666e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Thu, 24 Jun 2021 07:17:09 +0200 Subject: [PATCH 4/4] [SE-0296] Fix typo --- proposals/0296-async-await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/0296-async-await.md b/proposals/0296-async-await.md index f8a57fa75b..47ae941e15 100644 --- a/proposals/0296-async-await.md +++ b/proposals/0296-async-await.md @@ -512,7 +512,7 @@ func f() async { In non-`async` functions, and closures without any `await` expression, the compiler selects the non-`async` overload: -``` +```swift func f() async { let f2 = { // In a synchronous context, the non-async overload is preferred: