Commit Graph

10 Commits

Author SHA1 Message Date
Josh Soref
bb8674df51 spelling: optional
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-04-21 15:53:30 -04:00
Josh Soref
f7236bc2e9 spelling: handler
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
2022-04-21 15:53:30 -04:00
Hamish Knight
06908adb08 [Async Refactoring] Handle multiple trailing closures
Update the trailing closure handling logic to
handle multiple trailing closures, and adjust the
refactoring output to surround the call in
parentheses rather than adding '.self'. This allows
the parser to deal with the multiple trailing
closures and also silences a warning that would
previously occur.

rdar://81230908
2021-07-28 20:50:40 +01:00
Hamish Knight
91c9b189cf [test] Change a %refactor into %refactor-check-compiles 2021-07-28 20:50:39 +01:00
Ben Barham
fabb02100f [Test] Add @escaping to async refactoring tests
The async refactorings ignore whether a completion handler had
`@escaping` or not. In preparation of fixing this, fix up all functions
to have `@escaping` for their completion handler parameter.

Also some small miscellaneous fixes in order to reduce the number of
warnings output on test failures and also the addition of `REQUIRES:
concurrency` on all tests.
2021-07-24 09:53:17 +10:00
Alex Hoppen
cd31fa0f73 [Async Refactoring] Improve fatalError message to unwrap result arguments in continuation
In the previous `fatalError` message `Expected non-nil result 'result' for nil error`, it wasn’t exactly clear wht the `error` referred to. In some cases, when the error was ignored, there wasn’t even an `error` variable in the refactored code.

The new `Expected non-nil result '...' in the non-error case` is a little more generic and also fits if an error is ignored.
2021-07-07 12:05:37 +02:00
Alex Hoppen
edc1393291 [Async Refactoring] Rename 'success param/argument' to 'result' in fatalError messages
The 'success param/argument' is a terminology we use internally in the refactoring and not one we want to present to the user. Just name it 'result' instead.
2021-07-07 11:54:11 +02:00
Alex Hoppen
6cbbb7cb1f [Async Refactoring] Split ambiguous (error + success) completion handler calls into success and error case
Previously, when a completion handler call had arguments for both the success and error parameters, we were always interpreting it as an error call. In case the error argument was an `Optional`, this could cause us to generate code that didn't compile, because we `throw`ed the `Error?` or passed it to `continuation.resume(throwing)`, both of which didn't work.

We now generate an `if let` statement that checks if the error is `nil`. If it is, the error is thrown. Otherwise, we interpret the call as a success call and return the result.

- Example 1 (convert to continuation)
-- Base Code
```swift
func test(completionHandler: (Int?, Error?) -> Void) {
  withoutAsyncAlternativeThrowing { (theValue, theError) in
    completionHandler(theValue, theError)
  }
}
```
-- Old Refactoring Result
```swift
func test() async throws -> Int {
  return try await withCheckedThrowingContinuation { continuation in
    withoutAsyncAlternativeThrowing { (theValue, theError) in
      continuation.resume(throwing: theError) // error: Argument type 'Error?' does not conform to expected type 'Error'
    }
  }
}
-- New Refactoring Result
```swift
func testThrowingContinuationRelayingErrorAndResult() async throws -> Int {
  return try await withCheckedThrowingContinuation { continuation in
    withoutAsyncAlternativeThrowing { (theValue, theError) in
      if let error = theError {
        continuation.resume(throwing: error)
      } else {
        guard let theValue = theValue else {
          fatalError("Expected non-nil success argument 'theValue' for nil error")
        }
        continuation.resume(returning: theValue)
      }
    }
  }
}
```

- Example 2 (convert to async/await)
-- Base Code
```swift
func test(completion: (String?, Error?) -> Void) {
  simpleErr() { (res, err) in
    completion(res, err)
  }
}
```
-- Old Refactoring Result
```swift
func test() async throws -> String {
  let res = try await simpleErr()
  throw <#err#>
}
```
-- New Refactoring Result
```swift
func test() async throws -> String {
  let res = try await simpleErr()
  return res
}
```
2021-07-07 11:54:11 +02:00
Alex Hoppen
ec8957972a [Async Refactoring] Get semantics providing expr to decide if call is to completion handler
Resolves rdar://78011350
2021-07-05 17:41:39 +02:00
Alex Hoppen
54fcc90841 [Async Refactoring] Wrap code in a continuation if conversion doesn't yield reasonable results
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
2021-07-02 15:48:41 +02:00