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
This commit is contained in:
Hamish Knight
2021-06-05 18:41:13 +01:00
parent 34402346be
commit f28284dcc3
2 changed files with 722 additions and 50 deletions

View File

@@ -5,6 +5,432 @@ enum E : Error { case e }
func anyCompletion(_ completion: (Any?, Error?) -> Void) {}
func anyResultCompletion(_ completion: (Result<Any?, Error>) -> Void) {}
func stringTupleParam(_ completion: ((String, String)?, Error?) -> Void) {}
func stringTupleParam() async throws -> (String, String) {}
func stringTupleResult(_ completion: (Result<(String, String), Error>) -> Void) {}
func stringTupleResult() async throws -> (String, String) {}
func mixedTupleResult(_ completion: (Result<((Int, Float), String), Error>) -> Void) {}
func mixedTupleResult() async throws -> ((Int, Float), String) {}
func multipleTupleParam(_ completion: ((String, String)?, (Int, Int)?, Error?) -> Void) {}
func multipleTupleParam() async throws -> ((String, String), (Int, Int)) {}
func testPatterns() async throws {
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE %s
stringTupleParam { strs, err in
guard let (str1, str2) = strs else { return }
print(str1, str2)
}
// INLINE: let (str1, str2) = try await stringTupleParam()
// INLINE-NEXT: print(str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE-VAR %s
stringTupleParam { strs, err in
guard var (str1, str2) = strs else { return }
print(str1, str2)
}
// INLINE-VAR: var (str1, str2) = try await stringTupleParam()
// INLINE-VAR-NEXT: print(str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE-BLANK %s
stringTupleParam { strs, err in
guard var (_, str2) = strs else { return }
print(str2)
}
// INLINE-BLANK: var (_, str2) = try await stringTupleParam()
// INLINE-BLANK-NEXT: print(str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE-TYPED %s
stringTupleParam { strs, err in
guard let (str1, str2): (String, String) = strs else { return }
print(str1, str2)
}
// INLINE-TYPED: let (str1, str2) = try await stringTupleParam()
// INLINE-TYPED-NEXT: print(str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE-CASE %s
stringTupleParam { strs, err in
guard case (let str1, var str2)? = strs else { return }
print(str1, str2)
}
// INLINE-CASE: var (str1, str2) = try await stringTupleParam()
// INLINE-CASE-NEXT: print(str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=INLINE-CASE-TYPED %s
stringTupleParam { strs, err in
guard case let (str1, str2)?: (String, String)? = strs else { return }
print(str1, str2)
}
// INLINE-CASE-TYPED: let (str1, str2) = try await stringTupleParam()
// INLINE-CASE-TYPED-NEXT: print(str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=OUT-OF-LINE %s
stringTupleParam { strs, err in
guard let (str1, str2) = strs else { return }
print(str1, str2, strs!)
}
// OUT-OF-LINE: let strs = try await stringTupleParam()
// OUT-OF-LINE-NEXT: let (str1, str2) = strs
// OUT-OF-LINE-NEXT: print(str1, str2, strs)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=OUT-OF-LINE-VAR %s
stringTupleParam { strs, err in
guard var (str1, _) = strs else { return }
str1 = ""
print(str1, {strs!})
}
// OUT-OF-LINE-VAR: let strs = try await stringTupleParam()
// OUT-OF-LINE-VAR-NEXT: var (str1, _) = strs
// OUT-OF-LINE-VAR-NEXT: str1 = ""
// OUT-OF-LINE-VAR-NEXT: print(str1, {strs})
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=OUT-OF-LINE-CASE %s
stringTupleParam { strs, err in
guard case (let str1, var str2)? = strs else { return }
print(str1, str2, strs!)
}
// OUT-OF-LINE-CASE: let strs = try await stringTupleParam()
// OUT-OF-LINE-CASE-NEXT: var (str1, str2) = strs
// OUT-OF-LINE-CASE-NEXT: print(str1, str2, strs)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=FALLBACK %s
stringTupleParam { strs, err in
guard let (str1, str2) = strs, str1 == "hi" else { fatalError() }
print(str1, str2, err)
}
// FALLBACK: var strs: (String, String)? = nil
// FALLBACK-NEXT: var err: Error? = nil
// FALLBACK-NEXT: do {
// FALLBACK-NEXT: strs = try await stringTupleParam()
// FALLBACK-NEXT: } catch {
// FALLBACK-NEXT: err = error
// FALLBACK-NEXT: }
// FALLBACK-EMPTY:
// FALLBACK-NEXT: guard let (str1, str2) = strs, str1 == "hi" else { fatalError() }
// FALLBACK-NEXT: print(str1, str2, err)
// FIXME: Arguably we should be able to classify everything after the guard as
// a success path and avoid the fallback in this case.
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=FALLBACK2 %s
stringTupleParam { strs, err in
guard let (str1, str2) = strs else { fatalError() }
print(str1, str2)
if .random(), err == nil {
print("yay")
} else {
print("nay")
}
}
// FALLBACK2: var strs: (String, String)? = nil
// FALLBACK2-NEXT: var err: Error? = nil
// FALLBACK2-NEXT: do {
// FALLBACK2-NEXT: strs = try await stringTupleParam()
// FALLBACK2-NEXT: } catch {
// FALLBACK2-NEXT: err = error
// FALLBACK2-NEXT: }
// FALLBACK2-EMPTY:
// FALLBACK2-NEXT: guard let (str1, str2) = strs else { fatalError() }
// FALLBACK2-NEXT: print(str1, str2)
// FALLBACK2-NEXT: if .random(), err == nil {
// FALLBACK2-NEXT: print("yay")
// FALLBACK2-NEXT: } else {
// FALLBACK2-NEXT: print("nay")
// FALLBACK2-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-BINDINGS %s
stringTupleParam { strs, err in
guard let x = strs else { return }
guard var (str1, str2) = strs else { return }
guard var y = strs else { return }
guard let z = strs else { return }
y = ("hello", "there")
print(x, y, z, str1, str2)
}
// Make sure that we
// 1. Coalesce the z binding, as it is a let
// 2. Preserve the y binding, as it is a var
// 3. Print the multi-var binding out of line
//
// MIXED-BINDINGS: let x = try await stringTupleParam()
// MIXED-BINDINGS-NEXT: var (str1, str2) = x
// MIXED-BINDINGS-NEXT: var y = x
// MIXED-BINDINGS-NEXT: y = ("hello", "there")
// MIXED-BINDINGS-NEXT: print(x, y, x, str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-BINDINGS2 %s
stringTupleParam { strs, err in
guard var (str1, str2) = strs else { return }
str1 = "hi"
guard var x = strs else { return }
x = ("hello", "there")
print(x, str1, str2)
}
// MIXED-BINDINGS2: var x = try await stringTupleParam()
// MIXED-BINDINGS2-NEXT: var (str1, str2) = x
// MIXED-BINDINGS2-NEXT: str1 = "hi"
// MIXED-BINDINGS2-NEXT: x = ("hello", "there")
// MIXED-BINDINGS2-NEXT: print(x, str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-BINDINGS3 %s
stringTupleParam { strs, err in
guard let (str1, str2) = strs else { return }
guard let x = strs else { return }
print(x, str1, str2)
}
// MIXED-BINDINGS3: let x = try await stringTupleParam()
// MIXED-BINDINGS3-NEXT: let (str1, str2) = x
// MIXED-BINDINGS3-NEXT: print(x, str1, str2)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=ALIAS-BINDINGS %s
stringTupleParam { strs, err in
guard let x = strs else { return }
guard let y = strs else { return }
guard let z = strs else { return }
print(x, y, z)
}
// ALIAS-BINDINGS: let x = try await stringTupleParam()
// ALIAS-BINDINGS-NEXT: print(x, x, x)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=ALIAS-BINDINGS2 %s
stringTupleParam { strs, err in
guard var x = strs else { return }
guard var y = strs else { return }
guard let z = strs else { return }
print(x, y, z)
}
// ALIAS-BINDINGS2: let z = try await stringTupleParam()
// ALIAS-BINDINGS2-NEXT: var x = z
// ALIAS-BINDINGS2-NEXT: var y = z
// ALIAS-BINDINGS2-NEXT: print(x, y, z)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=ALIAS-BINDINGS3 %s
stringTupleParam { strs, err in
guard var x = strs else { return }
guard var y = strs else { return }
guard var z = strs else { return }
print(x, y, z)
}
// ALIAS-BINDINGS3: var x = try await stringTupleParam()
// ALIAS-BINDINGS3-NEXT: var y = x
// ALIAS-BINDINGS3-NEXT: var z = x
// ALIAS-BINDINGS3-NEXT: print(x, y, z)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=ALIAS-BINDINGS4 %s
stringTupleParam { strs, err in
guard var x = strs else { return }
guard let y = strs else { return }
guard let z = strs else { return }
print(x, y, z)
}
// ALIAS-BINDINGS4: let y = try await stringTupleParam()
// ALIAS-BINDINGS4-NEXT: var x = y
// ALIAS-BINDINGS4-NEXT: print(x, y, y)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=ALIAS-BINDINGS5 %s
stringTupleParam { strs, err in
guard var x = strs else { return }
print(x)
}
// ALIAS-BINDINGS5: var x = try await stringTupleParam()
// ALIAS-BINDINGS5-NEXT: print(x)
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=STRING-TUPLE-RESULT %s
stringTupleResult { res in
switch res {
case .success((let x, let y)):
print(x, y)
case .failure:
print("oh no")
}
}
// STRING-TUPLE-RESULT: do {
// STRING-TUPLE-RESULT-NEXT: let (x, y) = try await stringTupleResult()
// STRING-TUPLE-RESULT-NEXT: print(x, y)
// STRING-TUPLE-RESULT-NEXT: } catch {
// STRING-TUPLE-RESULT-NEXT: print("oh no")
// STRING-TUPLE-RESULT-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-TUPLE-RESULT %s
mixedTupleResult { res in
if case .failure(let err) = res {
print("oh no")
}
if case .success(((let x, let y), let z)) = res {
print("a", x, y, z)
}
switch res {
case .success(let ((x, _), z)):
print("b", x, z)
case .failure:
print("oh no again")
}
}
// MIXED-TUPLE-RESULT: do {
// MIXED-TUPLE-RESULT-NEXT: let res = try await mixedTupleResult()
// MIXED-TUPLE-RESULT-NEXT: let ((x, y), z) = res
// MIXED-TUPLE-RESULT-NEXT: let ((x1, _), z1) = res
// MIXED-TUPLE-RESULT-NEXT: print("a", x, y, z)
// MIXED-TUPLE-RESULT-NEXT: print("b", x, z)
// MIXED-TUPLE-RESULT-NEXT: } catch let err {
// MIXED-TUPLE-RESULT-NEXT: print("oh no")
// MIXED-TUPLE-RESULT-NEXT: print("oh no again")
// MIXED-TUPLE-RESULT-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-TUPLE-RESULT2 %s
mixedTupleResult { res in
switch res {
case .success(((let x, let _), let z)):
print(x, z)
case .failure(let err):
print("oh no \(err)")
}
}
// MIXED-TUPLE-RESULT2: do {
// MIXED-TUPLE-RESULT2-NEXT: let ((x, _), z) = try await mixedTupleResult()
// MIXED-TUPLE-RESULT2-NEXT: print(x, z)
// MIXED-TUPLE-RESULT2-NEXT: } catch let err {
// MIXED-TUPLE-RESULT2-NEXT: print("oh no \(err)")
// MIXED-TUPLE-RESULT2-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MIXED-TUPLE-RESULT3 %s
mixedTupleResult { res in
if let ((_, y), z) = try? res.get() {
print(y, z)
} else {
print("boo")
}
}
// MIXED-TUPLE-RESULT3: do {
// MIXED-TUPLE-RESULT3-NEXT: let ((_, y), z) = try await mixedTupleResult()
// MIXED-TUPLE-RESULT3-NEXT: print(y, z)
// MIXED-TUPLE-RESULT3-NEXT: } catch {
// MIXED-TUPLE-RESULT3-NEXT: print("boo")
// MIXED-TUPLE-RESULT3-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MULTIPLE-TUPLE-PARAM %s
multipleTupleParam { strs, ints, err in
guard let (str1, str2) = strs, let (int1, int2) = ints else {
print("ohno")
return
}
print(str1, str2, int1, int2)
}
// MULTIPLE-TUPLE-PARAM: do {
// MULTIPLE-TUPLE-PARAM-NEXT: let ((str1, str2), (int1, int2)) = try await multipleTupleParam()
// MULTIPLE-TUPLE-PARAM-NEXT: print(str1, str2, int1, int2)
// MULTIPLE-TUPLE-PARAM-NEXT: } catch let err {
// MULTIPLE-TUPLE-PARAM-NEXT: print("ohno")
// MULTIPLE-TUPLE-PARAM-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MULTIPLE-TUPLE-PARAM2 %s
multipleTupleParam { strs, ints, err in
guard let (str1, str2) = strs, var (int1, int2) = ints else {
print("ohno")
return
}
print(str1, str2, int1, int2)
}
// MULTIPLE-TUPLE-PARAM2: do {
// MULTIPLE-TUPLE-PARAM2-NEXT: var ((str1, str2), (int1, int2)) = try await multipleTupleParam()
// MULTIPLE-TUPLE-PARAM2-NEXT: print(str1, str2, int1, int2)
// MULTIPLE-TUPLE-PARAM2-NEXT: } catch let err {
// MULTIPLE-TUPLE-PARAM2-NEXT: print("ohno")
// MULTIPLE-TUPLE-PARAM2-NEXT: }
// RUN: %refactor-check-compiles -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=MULTIPLE-TUPLE-PARAM3 %s
multipleTupleParam { strs, ints, err in
guard let (str1, str2) = strs, let (int1, int2) = ints else {
print("ohno")
return
}
print(strs!)
print(str1, str2, int1, int2)
}
// MULTIPLE-TUPLE-PARAM3: do {
// MULTIPLE-TUPLE-PARAM3-NEXT: let (strs, (int1, int2)) = try await multipleTupleParam()
// MULTIPLE-TUPLE-PARAM3-NEXT: let (str1, str2) = strs
// MULTIPLE-TUPLE-PARAM3-NEXT: print(strs)
// MULTIPLE-TUPLE-PARAM3-NEXT: print(str1, str2, int1, int2)
// MULTIPLE-TUPLE-PARAM3-NEXT: } catch let err {
// MULTIPLE-TUPLE-PARAM3-NEXT: print("ohno")
// MULTIPLE-TUPLE-PARAM3-NEXT: }
}
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=NAME-COLLISION %s
func testNameCollision(_ completion: () -> Void) {
let a = ""
stringTupleParam { strs, err in
guard let (a, b) = strs else { return }
print(a, b)
}
let b = ""
stringTupleParam { strs, err in
guard let (a, b) = strs else { return }
print(a, b, strs!)
}
print(a, b)
completion()
}
// TODO: `throws` isn't added to the function declaration
// NAME-COLLISION: func testNameCollision() async {
// NAME-COLLISION-NEXT: let a = ""
// NAME-COLLISION-NEXT: let (a1, b) = try await stringTupleParam()
// NAME-COLLISION-NEXT: print(a1, b)
// NAME-COLLISION-NEXT: let b1 = ""
// NAME-COLLISION-NEXT: let strs = try await stringTupleParam()
// NAME-COLLISION-NEXT: let (a2, b2) = strs
// NAME-COLLISION-NEXT: print(a2, b2, strs)
// NAME-COLLISION-NEXT: print(a, b1)
// NAME-COLLISION-NEXT: return
// NAME-COLLISION-NEXT: }
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=NAME-COLLISION2 %s
func testNameCollision2(_ completion: () -> Void) {
mixedTupleResult { res in
guard case let .success((x, y), z) = res else { return }
stringTupleParam { strs, err in
if let (x, y) = strs {
print("a", x, y)
}
}
print("b", x, y, z)
}
}
// TODO: `throws` isn't added to the function declaration
// NAME-COLLISION2: func testNameCollision2() async {
// NAME-COLLISION2-NEXT: let ((x, y), z) = try await mixedTupleResult()
// NAME-COLLISION2-NEXT: let (x1, y1) = try await stringTupleParam()
// NAME-COLLISION2-NEXT: print("a", x1, y1)
// NAME-COLLISION2-NEXT: print("b", x, y, z)
// NAME-COLLISION2-NEXT: }
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=TEST-UNHANDLED %s
anyCompletion { val, err in
if let x = val {
@@ -118,3 +544,18 @@ anyResultCompletion { res in
print("oh no")
}
}
// Make sure we handle a capture list okay.
class C {
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=CAPTURE %s
func foo() {
let _ = { [weak self] in
print(self!)
}
}
}
// CAPTURE: func foo() async {
// CAPTURE-NEXT: let _ = { [weak self] in
// CAPTURE-NEXT: print(self!)
// CAPTURE-NEXT: }
// CAPTURE-NEXT: }