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

562 lines
20 KiB
Swift

// RUN: %empty-directory(%t)
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 {
print("a")
}
if let _ = val {
print("b")
}
if case let x? = val {
print("c")
}
if case let _? = val {
print("d")
}
if case E.e? = val {
print("e")
}
if case (let x as String)? = val {
print("f")
}
if case let "" as String = val {
print("g")
}
}
// TEST-UNHANDLED: let x = try await anyCompletion()
// TEST-UNHANDLED-NEXT: print("a")
// TEST-UNHANDLED-NEXT: print("b")
// TEST-UNHANDLED-NEXT: print("c")
// TEST-UNHANDLED-NEXT: print("d")
// TEST-UNHANDLED-NEXT: if case E.e? = <#x#> {
// TEST-UNHANDLED-NEXT: print("e")
// TEST-UNHANDLED-NEXT: }
// TEST-UNHANDLED-NEXT: if case (let x as String)? = <#x#> {
// TEST-UNHANDLED-NEXT: print("f")
// TEST-UNHANDLED-NEXT: }
// TEST-UNHANDLED-NEXT: if case let "" as String = <#x#> {
// TEST-UNHANDLED-NEXT: print("g")
// TEST-UNHANDLED-NEXT: }
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success(let unwrapped?):
print(unwrapped)
case .success(let x):
print(x)
case .failure:
print("oh no")
}
}
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success(let str as String):
print(str)
case .success(let x):
print(x)
case .failure:
print("oh no")
}
}
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success(E.e?):
print("ee")
case .success(let x):
print(x)
case .failure:
print("oh no")
}
}
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success("" as String):
print("empty string")
case .success(let x):
print(x)
case .failure:
print("oh no")
}
}
// FIXME: This should ideally be turned into a 'catch let e as E'.
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success(let a):
print(a)
case .failure(let e as E):
print("oh no e")
case .failure:
print("oh no")
}
}
// FIXME: This should ideally be turned into a 'catch E.e'.
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):1
anyResultCompletion { res in
switch res {
case .success(let a):
print(a)
case .failure(E.e):
print("oh no ee")
case .failure:
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: }