If we are requested to convert a function to async, but the call in the function’s body that eventually calls the completion handler doesn’t have an async alternative, we are currently copying the call as-is, replacing any calls to the completion handler by placeholders.
For example,
```swift
func testDispatch(completionHandler: @escaping (Int) -> Void) {
DispatchQueue.global.async {
completionHandler(longSyncFunc())
}
}
```
becomes
```swift
func testDispatch() async -> Int {
DispatchQueue.global.async {
<#completionHandler#>(longSyncFunc())
}
}
```
and
```swift
func testUrlSession(completionHandler: @escaping (Data) -> Void) {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
completion(data!)
}
task.resume()
}
```
becomes
```swift
func testUrlSession() async -> Data {
let task = URLSession.shared.dataTask(with: request) { data, response, error in
<#completion#>(data!)
}
task.resume()
}
```
Both of these are better modelled using continuations. Thus, if we find an expression that contains a call to the completion handler and can’t be hoisted to an await statement, we are wrapping the rest of the current scope in a `withChecked(Throwing)Continuation`, producing the following results:
```swift
func testDispatch() async -> Int {
return await withCheckedContinuation { (continuation: CheckedContinuation<Int, Never>) in
DispatchQueue.global.async {
continuation.resume(returning: syncComputation())
}
}
}
```
and
```swift
func testDataTask() async -> Int?
return await withCheckedContinuation { (continuation: CheckedContinuation<Data, Never>) in
let task = URLSession.shared.dataTask { data, response, error in
continuation.resume(returning: data!)
}
task.resume()
}
}
```
I think both are much closer to what the developer is actually expecting.
Resolves rdar://79304583
Previously, in the following case we were failing to add a `return` keyword inside `withImplicitReturn`.
```
func withImplicitReturn(completionHandler: (String) -> Void) {
simple {
completionHandler($0)
}
}
```
This is because the call of `completionHandler($0)` is wrapped by an implicit `ReturnStmt` and thus we assumed that there was already a `return` keyword present.
Fix this issue by checking if the wrapping `ReturnStmt` is implicit and if it is, add the `return` keyword.
Fixes rdar://80009760
If the completion handler parameter has a default
argument, mark the resulting async function as
`@discardableResult`, as the caller may not care
about the result.
rdar://79190170
The async refactoring may perform recursive AST
walks in cases such as transforming the body of
a hoisted closure. Make sure this can be handled
by the logic tracking the ASTWalker in the
SourceEntityWalker, such that we don't crash when
later converting the call to a completion callback.
rdar://78693050
Previously we were unconditionally dropping a
return statement if it was the last node, which
could cause us to inadvertently drop the result
expression as well. Instead, only drop bare
'return' statements.
rdar://77789360
It's possible the user has already written an
explicit return for the call to the completion
handler. In that case, avoid adding another return.
rdar://77789360
Previously we would drop comments between nodes in
a BraceStmt, as we printed each node out individually.
To remedy this, always make sure we scan backwards
to find any preceding comments attached to a node,
and also keep track of any SourceLocs which we
don't print, but may have comments attached which
we want to preserve.
rdar://77401810
Convert function to async currently only adds "async" to the function and runs the convert call refactoring on the body.
This was intentional, but it turns out to be somewhat confusing. Instead, run the same refactoring as the add async alternative refactoring but just replace rather than add.
Resolves rdar://77103049
When converting a function with a completion handler
that has a Void success parameter, e.g
`(Void?, Error?) -> Void`, or more likely a
`Result<Void, Error>` parameter, make sure to omit
the `-> Void` from the resulting async function
conversion.
In addition, strip any Void bindings from an async
function call, and any explicit Void return values
from inside the async function.
Resolves rdar://75189289
The replacement range was `FunctionDecl::getRange`, which does not
include attributes. This would cause attributes to be duplicated when
converting a function to async. Override the start with the attribute
start instead so that they are replaced as well.
Resolves rdar://74063741
Adds three refactorings intended to help users migrate their existing
code to use the new async language features:
1. Convert call to use async alternative
2. Convert function to async
3. Add async alternative function
A function is considered to have an async alternative if it has a void
return type and has a void returning closure as its last parameter. A
method to explicitly mark functions as having an async alternative may
be added to make this more accurate in the future (required for eg.
a warning about a call to the non-async version of a function in an
async context).
(1) converts a call to use the new `await` async language syntax. If the
async alternative throws, it will also add `try`. The closure itself is
hoisted out of the call, see the comments on
`AsyncConversionStringBuilder` for specifics.
(2) converts a whole function to `async`, using (1) to convert any calls
in the function to their async alternatives. (3) is similar to (2), but
instead *adds* a function and replaces calls to its
completion/handler/callback closure parameter with `return` or `throws`.
Resolves rdar://68254700