Files
swift-mirror/test/refactoring/ConvertAsync/variable_as_callback.swift
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

234 lines
14 KiB
Swift

enum CustomError: Error {
case invalid
case insecure
}
typealias SomeCallback = (String) -> Void
func simple(completion: (String) -> Void) { }
func simpleWithArg(a: Int, completion: (String) -> Void) { }
func multipleResults(completion: (String, Int) -> Void) { }
func nonOptionalError(completion: (String, Error) -> Void) { }
func noParams(completion: () -> Void) { }
func error(completion: (String?, Error?) -> Void) { }
func errorOnly(completion: (Error?) -> Void) { }
func errorNonOptionalResult(completion: (String, Error?) -> Void) { }
func alias(completion: SomeCallback) { }
func simpleResult(completion: (Result<String, Never>) -> Void) { }
func errorResult(completion: (Result<String, Error>) -> Void) { }
func customErrorResult(completion: (Result<String, CustomError>) -> Void) { }
func optionalSingle(completion: (String?) -> Void) { }
func manyOptional(_ completion: (String?, Int?) -> Void) { }
func generic<T, R>(completion: (T, R) -> Void) { }
func genericResult<T>(completion: (T?, Error?) -> Void) where T: Numeric { }
func genericError<E>(completion: (String?, E?) -> Void) where E: Error { }
func defaultArgs(a: Int, b: Int = 10, completion: (String) -> Void) { }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=SIMPLE-WITH-VARIABLE-COMPLETION-HANDLER %s
func testSimpleWithVariableCompletionHandler(completionHandler: (String) -> Void) {
simple(completion: completionHandler)
}
// SIMPLE-WITH-VARIABLE-COMPLETION-HANDLER: let result = await simple()
// SIMPLE-WITH-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=SIMPLE-WITH-ARG-VARIABLE-COMPLETION-HANDLER %s
func testSimpleWithArgVariableCompletionHandler(b: Int, completionHandler: (String) -> Void) {
simpleWithArg(a: b, completion: completionHandler)
}
// SIMPLE-WITH-ARG-VARIABLE-COMPLETION-HANDLER: let result = await simpleWithArg(a: b)
// SIMPLE-WITH-ARG-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=SIMPLE-WITH-CONSTANT-ARG-VARIABLE-COMPLETION-HANDLER %s
func testSimpleWithConstantArgVariableCompletionHandler(completionHandler: (String) -> Void) {
simpleWithArg(a: 1, completion: completionHandler)
}
// SIMPLE-WITH-CONSTANT-ARG-VARIABLE-COMPLETION-HANDLER: let result = await simpleWithArg(a: 1)
// SIMPLE-WITH-CONSTANT-ARG-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=MULTIPLE-RESULTS-VARIABLE-COMPLETION-HANDLER %s
func testMultipleResultsVariableCompletionHandler(completionHandler: (String, Int) -> Void) {
multipleResults(completion: completionHandler)
}
// MULTIPLE-RESULTS-VARIABLE-COMPLETION-HANDLER: let result = await multipleResults()
// MULTIPLE-RESULTS-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result.0, result.1)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=NON-OPTIONAL-ERROR-VARIABLE-COMPLETION-HANDLER %s
func testNonOptionalErrorVariableCompletionHandler(completionHandler: (String, Error) -> Void) {
nonOptionalError(completion: completionHandler)
}
// NON-OPTIONAL-ERROR-VARIABLE-COMPLETION-HANDLER: let result = await nonOptionalError()
// NON-OPTIONAL-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result.0, result.1)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=NO-PARAMS-VARIABLE-COMPLETION-HANDLER %s
func testNoParamsVariableCompletionHandler(completionHandler: () -> Void) {
noParams(completion: completionHandler)
}
// NO-PARAMS-VARIABLE-COMPLETION-HANDLER: await noParams()
// NO-PARAMS-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler()
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=ERROR-VARIABLE-COMPLETION-HANDLER %s
func testErrorWithVariableCompletionHandler(completionHandler: (String?, Error?) -> Void) {
error(completion: completionHandler)
}
// ERROR-VARIABLE-COMPLETION-HANDLER: do {
// ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: let result = try await error()
// ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result, nil)
// ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(nil, error)
// ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=ERROR-ONLY-VARIABLE-COMPLETION-HANDLER %s
func testErrorOnlyWithVariableCompletionHandler(completionHandler: (Error?) -> Void) {
errorOnly(completion: completionHandler)
}
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER: do {
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER-NEXT: try await errorOnly()
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(nil)
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(error)
// ERROR-ONLY-VARIABLE-COMPLETION-HANDLER-NEXT: }
// FIXME: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3
func testErrorNonOptionalResultWithVariableCompletionHandler(completionHandler: (String, Error?) -> Void) {
errorNonOptionalResult(completion: completionHandler)
}
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=ALIAS-VARIABLE-COMPLETION-HANDLER %s
func testAliasWithVariableCompletionHandler(completionHandler: SomeCallback) {
alias(completion: completionHandler)
}
// ALIAS-VARIABLE-COMPLETION-HANDLER: let result = await alias()
// ALIAS-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=SIMPLE-RESULT-VARIABLE-COMPLETION-HANDLER %s
func testSimpleResultVariableCompletionHandler(completionHandler: (Result<String, Never>) -> Void) {
simpleResult(completion: completionHandler)
}
// SIMPLE-RESULT-VARIABLE-COMPLETION-HANDLER: let result = await simpleResult()
// SIMPLE-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(.success(result))
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=ERROR-RESULT-VARIABLE-COMPLETION-HANDLER %s
func testErrorResultVariableCompletionHandler(completionHandler: (Result<String, Error>) -> Void) {
errorResult(completion: completionHandler)
}
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER: do {
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: let result = try await errorResult()
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(.success(result))
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(.failure(error))
// ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER %s
func testErrorResultVariableCompletionHandler(completionHandler: (Result<String, CustomError>) -> Void) {
customErrorResult(completion: completionHandler)
}
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER: do {
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: let result = try await customErrorResult()
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(.success(result))
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(.failure(error as! CustomError))
// CUSTOM-ERROR-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=OPTIONAL-SINGLE-VARIABLE-COMPLETION-HANDLER %s
func testOptionalSingleVariableCompletionHandler(completionHandler: (String?) -> Void) {
optionalSingle(completion: completionHandler)
}
// OPTIONAL-SINGLE-VARIABLE-COMPLETION-HANDLER: let result = await optionalSingle()
// OPTIONAL-SINGLE-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=MANY-OPTIONAL-VARIABLE-COMPLETION-HANDLER %s
func testManyOptionalVariableCompletionHandler(completionHandler: (String?, Int?) -> Void) {
manyOptional(completionHandler)
}
// MANY-OPTIONAL-VARIABLE-COMPLETION-HANDLER: let result = await manyOptional()
// MANY-OPTIONAL-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result.0, result.1)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=GENERIC-VARIABLE-COMPLETION-HANDLER %s
func testGenericVariableCompletionHandler<T, R>(completionHandler: (T, R) -> Void) {
generic(completion: completionHandler)
}
// GENERIC-VARIABLE-COMPLETION-HANDLER: let result: (T, R) = await generic()
// GENERIC-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result.0, result.1)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=SPECIALIZE-GENERIC-VARIABLE-COMPLETION-HANDLER %s
func testSpecializeGenericsVariableCompletionHandler(completionHandler: (String, Int) -> Void) {
generic(completion: completionHandler)
}
// SPECIALIZE-GENERIC-VARIABLE-COMPLETION-HANDLER: let result: (String, Int) = await generic()
// SPECIALIZE-GENERIC-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result.0, result.1)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER %s
func testGenericResultVariableCompletionHandler<T>(completionHandler: (T?, Error?) -> Void) where T: Numeric {
genericResult(completion: completionHandler)
}
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER: do {
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: let result: T = try await genericResult()
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result, nil)
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(nil, error)
// GENERIC-RESULT-VARIABLE-COMPLETION-HANDLER-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER %s
func testGenericErrorVariableCompletionHandler<MyGenericError>(completionHandler: (String?, MyGenericError?) -> Void) where MyGenericError: Error {
genericError(completion: completionHandler)
}
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER: do {
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: let result: String = try await genericError()
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result, nil)
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: } catch {
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(nil, error as! MyGenericError)
// GENERIC-ERROR-VARIABLE-COMPLETION-HANDLER-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=DEFAULT-ARGS-VARIABLE-COMPLETION-HANDLER %s
func testDefaultArgsVariableCompletionHandler(completionHandler: (String) -> Void) {
defaultArgs(a: 5, completion: completionHandler)
}
// DEFAULT-ARGS-VARIABLE-COMPLETION-HANDLER: let result = await defaultArgs(a: 5)
// DEFAULT-ARGS-VARIABLE-COMPLETION-HANDLER-NEXT: completionHandler(result)
func myPrint(_ message: String) {
print(message)
}
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=GLOBAL-FUNC-AS-COMPLETION-HANDLER %s
func testGlobalFuncAsCompletionHandler() {
simple(completion: myPrint)
}
// GLOBAL-FUNC-AS-COMPLETION-HANDLER: let result = await simple()
// GLOBAL-FUNC-AS-COMPLETION-HANDLER-NEXT: myPrint(result)
class Foo {
var foo: Foo
init(foo: Foo) {
self.foo = foo
}
func myFooPrint(_ message: String) {
print("FOO: \(message)")
}
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):5 | %FileCheck -check-prefix=MEMBER-FUNC-AS-COMPLETION-HANDLER %s
func testMethodAsCompletionHandler() {
simple(completion: myFooPrint)
}
// MEMBER-FUNC-AS-COMPLETION-HANDLER: let result = await simple()
// MEMBER-FUNC-AS-COMPLETION-HANDLER-NEXT: myFooPrint(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):5 | %FileCheck -check-prefix=MEMBER-FUNC-ON-OTHER-OBJECT-AS-COMPLETION-HANDLER %s
func testMethodOnOtherObjectAsCompletionHandler(foo: Foo) {
simple(completion: foo.myFooPrint)
}
// MEMBER-FUNC-ON-OTHER-OBJECT-AS-COMPLETION-HANDLER: let result = await simple()
// MEMBER-FUNC-ON-OTHER-OBJECT-AS-COMPLETION-HANDLER-NEXT: foo.myFooPrint(result)
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):5 | %FileCheck -check-prefix=MEMBER-FUNC-ON-NESTED-OTHER-OBJECT-AS-COMPLETION-HANDLER %s
func testMethodOnNestedOtherObjectAsCompletionHandler(foo: Foo) {
simple(completion: foo.foo.myFooPrint)
}
// MEMBER-FUNC-ON-NESTED-OTHER-OBJECT-AS-COMPLETION-HANDLER: let result = await simple()
// MEMBER-FUNC-ON-NESTED-OTHER-OBJECT-AS-COMPLETION-HANDLER-NEXT: foo.foo.myFooPrint(result)
}