Commit Graph

191 Commits

Author SHA1 Message Date
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
Alex Hoppen
d0472e1b21 [Async Refactoring] Code style improvements 2021-07-01 15:57:42 +02:00
Alex Hoppen
7a68a1d834 Merge pull request #38191 from ahoppen/pr/implict-return-async-refactoring
[Async Refactoring] Add `return` keyword if wrapping `ReturnStmt` is implicit
2021-07-01 15:40:00 +02:00
Alex Hoppen
f7c7599a8c [Async Refactoring] Add return keyword if wrapping ReturnStmt is implicit
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
2021-07-01 10:21:46 +02:00
Hamish Knight
0d4eb978da [Async Refactoring] Add missing null type check
Don't crash if we have a boolean condition without
a type, as that may occur in invalid code.

rdar://79864182
2021-06-28 16:03:54 +01:00
Alex Hoppen
ffbbbaffca Merge pull request #38052 from ahoppen/pr/use-closure-label-for-async-return-type
[Refactoring] Use internal completion handler labels for async function's return type
2021-06-25 12:57:00 +02:00
Alex Hoppen
d944b8bd49 [Refactoring] Use internal completion handler labels for async function's return type
If a completion handler specifies internal parameter labels, we can use those to label the elements of the tuple returned by the async alternative.

For example
```swift
func foo(completion: (_ first: String, _ second: String) -> Void) { }
```
gets refactored to
```swift
func foo() async -> (first: String, second: String) { }
```

Resolves rdar://77268040
2021-06-24 08:00:28 +02:00
Alex Hoppen
81b37fdb4c [Refactoring] Support refactoring for case let patterns
Previously we only supported `case` patterns that bound with a `let` inside the associated value like `case .success(let value)`. With this change, we also support `case let .success(value)`.

Fixes rdar://79279846 [SR-14772]
2021-06-14 12:31:01 +02:00
Hamish Knight
e97c781ca1 [Async Refactoring] Add @discardableResult for defaulted completion
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
2021-06-11 11:39:16 +01:00
Hamish Knight
0c21310ab6 [Async Refactoring] Better handle known condition paths
If we're going to be classifying nodes in either
an `else if` block or after a `guard` statement
with a known condition path, be more lenient with
unhandled conditions, as we already know what path
they should be classified along.

rdar://78564388
2021-06-09 21:27:08 +01:00
Hamish Knight
2dac5b2ec6 Strip an unused DiagnosticEngine param 2021-06-09 10:43:25 +01:00
Hamish Knight
6d34fd9ed1 Handle parent vars in async transform
If we're in a case stmt body, any DeclRefExprs
to vars bound by a pattern will actually be to an
implicit var decl that's declared for the body. We
therefore need to walk to its "parent" to get to
the var referenced by the pattern, which will have
the correct entries in the naming and placeholder
maps.
2021-06-09 10:43:25 +01:00
Hamish Knight
f28284dcc3 Better handle non-refutable patterns in async transform
Keep track of patterns that bind multiple vars and
print them out when converting an async call. If
the parameter being bound isn't referenced elsewhere,
we'll print the pattern inline as e.g:

```
let ((x, y), z) = await foo()
```

Otherwise, if the parameter is referenced elsewhere
in the block we'll print the pattern out of line,
such as:

```
let (res, z) = await foo()
let (x, y) = res
```

In addition, make sure to print var bindings out
of line if there's also a let binding, e.g:

```
let x = await foo()
var y = x
```

This ensures any mutations to y doesn't affect x.
If there's only a single var binding, we'll print
it inline.

rdar://77802560
2021-06-09 10:43:25 +01:00
Hamish Knight
34402346be Generalise addTupleOf
Allow it to iterate over an arbitrary container
type.
2021-06-09 10:43:25 +01:00
Hamish Knight
6368c74b94 Adapt ScopedDeclCollector to track number of references
Use a DenseMap to track the number of references to
a given decl in a scope.
2021-06-09 10:43:24 +01:00
Hamish Knight
e9ba6c7240 Allow SemaAnnotator to handle patterns
Add the necessary walking hooks, and fix
ReferenceCollector to use it.
2021-06-09 10:43:24 +01:00
Hamish Knight
2cfd2bf27d Don't handle unclassified switch cases for now
Previously we would silently add these to the
success block. For now, let's leave them
unhandled.
2021-06-09 10:43:24 +01:00
Hamish Knight
c818172df1 Don't handle refutable patterns in async transform
These aren't currently supported.
2021-06-09 10:43:24 +01:00
Hamish Knight
800a9c537c Add a couple of getSemanticsProviding calls 2021-06-09 10:43:24 +01:00
Hamish Knight
c3e487c6f4 Explicitly handle error case in fallback logic
I don't believe this case currently can come up,
but leave it explicitly unhandled for now so we
don't perform an erroneous transform if it ever
comes up in the future.
2021-06-09 10:43:24 +01:00
Alex Hoppen
58be2bfbea [Refactoring] Don't crash when converting a function to async that contains a call to init
We were trying to retrieve the name of all function calls in the body using `getBaseIdentifier`. But calls to `init` don’t have a base identifier, just a `DeclBaseName` (which is special). Work with the `DeclBaseName` internally to prevent the crash.

Fixes rdar://78024731 [SR-14637]
2021-05-31 12:13:07 +02:00
Hamish Knight
c8a28f4357 Merge pull request #37629 from hamishknight/ghost-of-objc-past 2021-05-28 15:37:25 +01:00
Hamish Knight
7df0786c38 [Refactoring] Support Obj-C style bool flag checks
Add an additional case to `CallbackCondition` to
support boolean checks, and add the necessary
classification logic to determine whether a bool
flag check is for a success or failure path.

The logic currently first checks to see if the
async alternative has a convention that specifies
which parameter the flag is, and whether it's a
success or failure flag. If so, it classifies the
flag check accordingly. If there's no async
convention specified, we use a heuristic to see if
the error parameter is force unwrapped in the
failure block. If so, we treat that as the error
path. If there's no force unwrap of the error, we
leave the condition unhandled.

rdar://74063899
2021-05-28 13:10:26 +01:00
Hamish Knight
39cc1cbb85 [Refactoring] Add some calls to getSemanticsProvidingExpr 2021-05-28 13:10:26 +01:00
Hamish Knight
5ed7cae06f [Refactoring] Rework how conditions are classified
- Expand ConditionType to include `.success` and
`.failure` patterns.
- Introduce ConditionPath to represent whether a
classified condition represents a success or
failure path.
- Add methods to CallbackClassifier that deal with
deciding whether a given condition is a success or
failure path.
- Use these methods to simplify `classifyConditional`.
2021-05-28 13:10:25 +01:00
Hamish Knight
f2d5f38c9b [Refactoring] Remove ConditionType::INVALID
This will make it a little nicer to switch on
ConditionType.
2021-05-28 13:10:25 +01:00
Hamish Knight
8d92241ca9 [Refactoring] Support async for function extraction
Adapt the `ThrowingEntityAnalyzer` to pick up any
`await` keywords and add an `async` to the extracted
function if necessary along with an `await` for its
call.

rdar://72199949
2021-05-26 12:40:35 +01:00
Hamish Knight
e25c39c5a5 [Refactoring] Add try for extracted function call
If we're extracting a function that throws, add
a `try` to the call.
2021-05-26 12:40:34 +01:00
Hamish Knight
10038b6fce [Refactoring] Add async wrapper refactoring action
This allows an async alternative function to be
created that forwards onto the user's completion
handler function through the use of
`withCheckedContinuation`/`withCheckedThrowingContinuation`.

rdar://77802486
2021-05-19 20:46:02 +01:00
Hamish Knight
e4a7a93c73 [Refactoring] Generalize addCallToAsyncMethod
Factor out an `addForwardingCallTo` method that
allows the printing of a call to the old completion
handler function, with a given replacement for the
completion handler arg. `addCallToAsyncMethod` then
calls this with an empty replacement for the handler,
removing it from the call.
2021-05-19 20:46:02 +01:00
Hamish Knight
555f8da8b1 [Refactoring] Add utility method for printing tuples 2021-05-19 20:46:01 +01:00
Hamish Knight
46fa6e5721 [AST] Improve BinaryExpr
Abstract away the TupleExpr gunk and expose
`getLHS` and `getRHS` accessors. This is in
preparation for completely expunging the use
of TupleExpr as an argument list.
2021-05-19 14:48:01 +01:00
Alex Hoppen
b0bade6711 Merge pull request #37417 from bnbarham/add-completionhandler-attribute
[Refactoring] Add @completionHandlerAsync to sync function
2021-05-14 17:09:58 +02:00
Ben Barham
762337cc9b [Refactoring] Add @completionHandlerAsync to sync function
When adding an async alternative, add the @completionHandlerAsync
attribute to the sync function. Check for this attribute in addition to
the name check, ie. convert a call if the callee has either
@completionHandlerAsync or a name that is completion-handler-like name.

The addition of the attribute is currently gated behind the experimental
concurrency flag.

Resolves rdar://77486504
2021-05-14 20:19:02 +10:00
Hamish Knight
7ab5915750 [Refactoring] Don't drop returns with expressions
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
2021-05-14 11:17:59 +01:00
Hamish Knight
aa189f2fe9 [Refactoring] Avoid duplicating return statements
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
2021-05-14 11:17:59 +01:00
Hamish Knight
d7d58cbbb2 [Refactoring] Tweak handling of return and break placeholders
- Add a missing return to the break statement
placeholder handling.

- Only turn the `return` token into a placeholder,
as we still want to apply the transformation to
the sub expression.

This stops us from crashing by attempting to walk
into the return sub-expression.

rdar://77789360
2021-05-14 11:17:57 +01:00
Hamish Knight
8cb319b640 [Refactoring] Preserve comments in async transform
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
2021-05-13 14:16:27 +01:00
Hamish Knight
3a7a880fa0 [Refactoring] Store SourceFile on AsyncConverter 2021-05-13 14:16:26 +01:00
Ben Barham
8569c8a51b [Refactoring] Avoid redeclarations or shadowing in async refactored code
When converting a call or function, rename declarations such that
redeclaration errors and shadowing are avoided. In some cases this will
be overly conservative, but since any renamed variable can be fixed with
edit all in scope, this is preferred over causing redeclaration errors
or possible shadowing.

Resolves rdar://73973517
2021-05-13 17:48:41 +10:00
Alex Hoppen
32ceb24b6c [Refactoring] Promote call to refactored completion handlers to return
When a function’s completion handler is being passed to another function and the function that the completion handler is being declared in is converted to async, replace the call to the completion handler by a `return` statement.

For example:
```swift
func foo(completion: (String) -> Void) {
  bar(completion)
}
```

becomes
```swift
func foo() async -> String {
  return await bar()
}
```

Previously, we were calling the completion handler, which no longer exists in the newly created `async` function.
2021-05-12 09:26:25 +02:00
Alex Hoppen
921443e8ab [Refactoring] Support creation of async legacy bodies even if a parameter is not optional
If the parameter is not an `Optional`, add a placeholder instead that the user can fill with a sensible default value.
2021-05-11 15:49:55 +02:00
Alex Hoppen
dd978cca0b [Refactoring] Support refactoring calls to async if a variable or function is used as completion handler
Previously, we only supported  refactoring a function to call the async alternative if a closure was used for the callback parameter. With this change, we also support calling a arbitrary function (or variable with function type) that is passed to the completion handler argument.

The implementation basically re-uses the code we already have to create the legacy function’s body (which calls the newly created async version and then forwards the arguments to the legacy completion handler).

To describe the completion handler that the result is being forwarded to, I’m also using `AsyncHandlerDesc`, but since the completion handler may be a variable, it doesn’t necessarily have an `Index` within a function decl that declares it. Because of this, I split the `AsyncHandlerDesc` up into a context-free `AsyncHandlerDesc` (without an `Index`) and `AsyncHandlerParamDesc` (which includes the `Index`). It turns out that `AsyncHandlerDesc` is sufficient in most places.

Resolves rdar://77460524
2021-05-11 15:48:24 +02:00
Alex Hoppen
b8ec77892c [Refactoring] Replace usage of constant strings with tok:: wherever possible
The code moved from `LegacyAlternativeBodyCreator` was using constant strings a lot. Use `tok::` instead to match the style of `AsyncConverter`.
2021-05-07 18:05:27 +02:00
Alex Hoppen
8903190bed [Refactoring] Merge LegacyAlternativeBodyCreator into AsyncConverter
This will later allow us to reuse parts of `LegacyAlternativeBodyCreator` from `AsyncConverter` when refactoring calls to an async alternative if they pass a variable as the completion handler.
2021-05-07 18:05:27 +02:00
Ben Barham
398124c61a [Refactoring] Only unwrap optionals if the handler has an optional error
Resolves rdar://73973459
2021-05-06 10:27:34 +10:00
Hamish Knight
69caeae420 Merge pull request #37189 from hamishknight/break-it-down-for-me 2021-05-05 10:07:11 +01:00
Alex Hoppen
e835b77956 Merge pull request #37185 from ahoppen/pr/legacy-async-method-refactor
[Refactoring] When adding an async alternative refactor the old method to call the async method using `async`
2021-05-04 18:33:25 +02:00
Hamish Knight
f5acd137e8 [Refactoring] Replace lifted breaks/returns with placeholders
If we're lifting them outside of the control flow
structure they're dealing with, turn them into
placeholders, as they will no longer perform the
control flow the user is expecting.

This handles:
- Return statements at the top-level of the callback.
- Break statements in switches that we re-write.

Resolves rdar://74014897.
2021-05-04 14:30:23 +01:00
Hamish Knight
8a052394aa [Refactoring] Don't transform unrelated switches
We were missing a `return` here to ignore any
switch statements that don't have anything to do
with the error handling.
2021-05-04 14:30:23 +01:00