[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
This commit is contained in:
Alex Hoppen
2021-07-01 15:57:49 +02:00
parent d0472e1b21
commit 54fcc90841
3 changed files with 685 additions and 73 deletions

View File

@@ -4183,6 +4183,8 @@ struct AsyncHandlerDesc {
} }
} }
HandlerType getHandlerType() const { return Type; }
/// Get the type of the completion handler. /// Get the type of the completion handler.
swift::Type getType() const { swift::Type getType() const {
if (auto Var = Handler.dyn_cast<const VarDecl *>()) { if (auto Var = Handler.dyn_cast<const VarDecl *>()) {
@@ -5697,6 +5699,33 @@ private:
} }
}; };
/// Checks whether an ASTNode contains a reference to a given declaration.
class DeclReferenceFinder : private SourceEntityWalker {
bool HasFoundReference = false;
const Decl *Search;
bool walkToExprPre(Expr *E) override {
if (auto DRE = dyn_cast<DeclRefExpr>(E)) {
if (DRE->getDecl() == Search) {
HasFoundReference = true;
return false;
}
}
return true;
}
DeclReferenceFinder(const Decl *Search) : Search(Search) {}
public:
/// Returns \c true if \p node contains a reference to \p Search, \c false
/// otherwise.
static bool containsReference(ASTNode Node, const ValueDecl *Search) {
DeclReferenceFinder Checker(Search);
Checker.walk(Node);
return Checker.HasFoundReference;
}
};
/// Builds up async-converted code for an AST node. /// Builds up async-converted code for an AST node.
/// ///
/// If it is a function, its declaration will have `async` added. If a /// If it is a function, its declaration will have `async` added. If a
@@ -5727,6 +5756,21 @@ private:
/// the code the user intended. In most cases the refactoring will continue, /// the code the user intended. In most cases the refactoring will continue,
/// with any unhandled decls wrapped in placeholders instead. /// with any unhandled decls wrapped in placeholders instead.
class AsyncConverter : private SourceEntityWalker { class AsyncConverter : private SourceEntityWalker {
struct Scope {
llvm::DenseSet<DeclBaseName> Names;
/// If this scope is wrapped in a \c withChecked(Throwing)Continuation, the
/// name of the continuation that must be resumed where there previously was
/// a call to the function's completion handler.
/// Otherwise an empty identifier.
Identifier ContinuationName;
Scope(Identifier ContinuationName)
: Names(), ContinuationName(ContinuationName) {}
/// Whether this scope is wrapped in a \c withChecked(Throwing)Continuation.
bool isWrappedInContination() const { return !ContinuationName.empty(); }
};
SourceFile *SF; SourceFile *SF;
SourceManager &SM; SourceManager &SM;
DiagnosticEngine &DiagEngine; DiagnosticEngine &DiagEngine;
@@ -5755,9 +5799,12 @@ class AsyncConverter : private SourceEntityWalker {
// declarations of old completion handler parametes, as well as the // declarations of old completion handler parametes, as well as the
// replacement for other hoisted declarations and their references // replacement for other hoisted declarations and their references
llvm::DenseMap<const Decl *, Identifier> Names; llvm::DenseMap<const Decl *, Identifier> Names;
// Names of decls in each scope, where the first element is the initial scope
// and the last is the current scope. /// The scopes (containing all name decls and whether the scope is wrapped in
llvm::SmallVector<llvm::DenseSet<DeclBaseName>, 4> ScopedNames; /// a continuation) as the AST is being walked. The first element is the
/// initial scope and the last is the current scope.
llvm::SmallVector<Scope, 4> Scopes;
// Mapping of \c BraceStmt -> declarations referenced in that statement // Mapping of \c BraceStmt -> declarations referenced in that statement
// without first being declared. These are used to fill the \c ScopeNames // without first being declared. These are used to fill the \c ScopeNames
// map on entering that scope. // map on entering that scope.
@@ -6149,6 +6196,58 @@ private:
addRange(LastAddedLoc, P->getEndLoc(), /*ToEndOfToken*/ true); addRange(LastAddedLoc, P->getEndLoc(), /*ToEndOfToken*/ true);
} }
/// Check whether \p Node requires the remainder of this scope to be wrapped
/// in a \c withChecked(Throwing)Continuation. If it is necessary, add
/// a call to \c withChecked(Throwing)Continuation and modify the current
/// scope (\c Scopes.back() ) so that it knows it's wrapped in a continuation.
///
/// Wrapping a node in a continuation is necessary if the following conditions
/// are satisfied:
/// - It contains a reference to the \c TopHandler's completion hander,
/// because these completion handler calls need to be promoted to \c return
/// statements in the refactored method, but
/// - We cannot hoist the completion handler of \p Node, because it doesn't
/// have an async alternative by our heuristics (e.g. because of a
/// completion handler name mismatch or because it also returns a value
/// synchronously).
void wrapScopeInContinationIfNecessary(ASTNode Node) {
if (NestedExprCount != 0) {
// We can't start a continuation in the middle of an expression
return;
}
if (Scopes.back().isWrappedInContination()) {
// We are already in a continuation. No need to add another one.
return;
}
if (!DeclReferenceFinder::containsReference(Node,
TopHandler.getHandler())) {
// The node doesn't have a reference to the function's completion handler.
// It can stay a call with a completion handler, because we don't need to
// promote a completion handler call to a 'return'.
return;
}
// Wrap the current call in a continuation
Identifier contName = createUniqueName("continuation");
Scopes.back().Names.insert(contName);
Scopes.back().ContinuationName = contName;
insertCustom(Node.getStartLoc(), [&]() {
OS << tok::kw_return << ' ';
if (TopHandler.HasError) {
OS << tok::kw_try << ' ';
}
OS << "await ";
if (TopHandler.HasError) {
OS << "withCheckedThrowingContinuation ";
} else {
OS << "withCheckedContinuation ";
}
OS << tok::l_brace << ' ' << contName << ' ' << tok::kw_in << '\n';
});
}
bool walkToPatternPre(Pattern *P) override { bool walkToPatternPre(Pattern *P) override {
// If we're not converting a pattern, there's nothing extra to do. // If we're not converting a pattern, there's nothing extra to do.
if (!ConvertingPattern) if (!ConvertingPattern)
@@ -6167,18 +6266,21 @@ private:
bool walkToDeclPre(Decl *D, CharSourceRange Range) override { bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
if (isa<PatternBindingDecl>(D)) { if (isa<PatternBindingDecl>(D)) {
// We can't hoist a closure inside a PatternBindingDecl. If it contains
// a call to the completion handler, wrap it in a continuation.
wrapScopeInContinationIfNecessary(D);
NestedExprCount++; NestedExprCount++;
return true; return true;
} }
// Functions and types already have their names in \c ScopedNames, only // Functions and types already have their names in \c Scopes.Names, only
// variables should need to be renamed. // variables should need to be renamed.
if (isa<VarDecl>(D)) { if (isa<VarDecl>(D)) {
// If we don't already have a name for the var, assign it one. Note that // If we don't already have a name for the var, assign it one. Note that
// vars in binding patterns may already have assigned names here. // vars in binding patterns may already have assigned names here.
if (Names.find(D) == Names.end()) { if (Names.find(D) == Names.end()) {
auto Ident = assignUniqueName(D, StringRef()); auto Ident = assignUniqueName(D, StringRef());
ScopedNames.back().insert(Ident); Scopes.back().Names.insert(Ident);
} }
addCustom(D->getSourceRange(), [&]() { addCustom(D->getSourceRange(), [&]() {
OS << newNameFor(D); OS << newNameFor(D);
@@ -6239,11 +6341,20 @@ private:
return addCustom(E->getSourceRange(), return addCustom(E->getSourceRange(),
[&]() { OS << newNameFor(D, true); }); [&]() { OS << newNameFor(D, true); });
} }
} else if (NestedExprCount == 0) { } else if (CallExpr *CE = TopHandler.getAsHandlerCall(E)) {
if (CallExpr *CE = TopHandler.getAsHandlerCall(E)) if (Scopes.back().isWrappedInContination()) {
return addCustom(CE->getSourceRange(),
[&]() { addHandlerCallToContinuation(CE); });
} else if (NestedExprCount == 0) {
return addCustom(CE->getSourceRange(), [&]() { addHandlerCall(CE); }); return addCustom(CE->getSourceRange(), [&]() { addHandlerCall(CE); });
}
if (auto *CE = dyn_cast<CallExpr>(E)) { } else if (auto *CE = dyn_cast<CallExpr>(E)) {
// Try and hoist a call's completion handler. Don't do so if
// - the current expression is nested (we can't start hoisting in the
// middle of an expression)
// - the current scope is wrapped in a continuation (we can't have await
// calls in the continuation block)
if (NestedExprCount == 0 && !Scopes.back().isWrappedInContination()) {
// If the refactoring is on the call itself, do not require the callee // If the refactoring is on the call itself, do not require the callee
// to have the @completionHandlerAsync attribute or a completion-like // to have the @completionHandlerAsync attribute or a completion-like
// name. // name.
@@ -6256,6 +6367,10 @@ private:
} }
} }
// We didn't do any special conversion for this expression. If needed, wrap
// it in a continuation.
wrapScopeInContinationIfNecessary(E);
NestedExprCount++; NestedExprCount++;
return true; return true;
} }
@@ -6319,18 +6434,37 @@ private:
} }
bool walkToStmtPost(Stmt *S) override { bool walkToStmtPost(Stmt *S) override {
if (startsNewScope(S)) if (startsNewScope(S)) {
ScopedNames.pop_back(); bool ClosedScopeWasWrappedInContinuation =
Scopes.back().isWrappedInContination();
Scopes.pop_back();
if (ClosedScopeWasWrappedInContinuation &&
!Scopes.back().isWrappedInContination()) {
// The nested scope was wrapped in a continuation but the current one
// isn't anymore. Add the '}' that corresponds to the the call to
// withChecked(Throwing)Continuation.
insertCustom(S->getEndLoc(), [&]() { OS << tok::r_brace << '\n'; });
}
}
return true; return true;
} }
bool addCustom(SourceRange Range, std::function<void()> Custom = {}) { bool addCustom(SourceRange Range, llvm::function_ref<void()> Custom = {}) {
addRange(LastAddedLoc, Range.Start); addRange(LastAddedLoc, Range.Start);
Custom(); Custom();
LastAddedLoc = Lexer::getLocForEndOfToken(SM, Range.End); LastAddedLoc = Lexer::getLocForEndOfToken(SM, Range.End);
return false; return false;
} }
/// Insert custom text at the given \p Loc that shouldn't replace any existing
/// source code.
bool insertCustom(SourceLoc Loc, llvm::function_ref<void()> Custom = {}) {
addRange(LastAddedLoc, Loc);
Custom();
LastAddedLoc = Loc;
return false;
}
void addRange(SourceLoc Start, SourceLoc End, bool ToEndOfToken = false) { void addRange(SourceLoc Start, SourceLoc End, bool ToEndOfToken = false) {
if (ToEndOfToken) { if (ToEndOfToken) {
OS << Lexer::getCharSourceRangeFromSourceRange(SM, OS << Lexer::getCharSourceRangeFromSourceRange(SM,
@@ -6439,6 +6573,9 @@ private:
void addDo() { OS << tok::kw_do << " " << tok::l_brace << "\n"; } void addDo() { OS << tok::kw_do << " " << tok::l_brace << "\n"; }
void addHandlerCall(const CallExpr *CE) { void addHandlerCall(const CallExpr *CE) {
assert(TopHandler.getAsHandlerCall(const_cast<CallExpr *>(CE)) == CE &&
"addHandlerCall must be used with a call to the TopHandler's "
"completion handler");
auto Exprs = TopHandler.extractResultArgs(CE); auto Exprs = TopHandler.extractResultArgs(CE);
bool AddedReturnOrThrow = true; bool AddedReturnOrThrow = true;
@@ -6462,20 +6599,61 @@ private:
if (!Args.empty()) { if (!Args.empty()) {
if (AddedReturnOrThrow) if (AddedReturnOrThrow)
OS << " "; OS << " ";
if (Args.size() > 1) unsigned I = 0;
OS << tok::l_paren; addTupleOf(Args, OS, [&](Expr *Elt) {
for (size_t I = 0, E = Args.size(); I < E; ++I) {
if (I > 0)
OS << tok::comma << " ";
// Can't just add the range as we need to perform replacements // Can't just add the range as we need to perform replacements
convertNode(Args[I], /*StartOverride=*/CE->getArgumentLabelLoc(I), convertNode(Elt, /*StartOverride=*/CE->getArgumentLabelLoc(I),
/*ConvertCalls=*/false); /*ConvertCalls=*/false);
} I++;
if (Args.size() > 1) });
OS << tok::r_paren;
} }
} }
/// Assuming that \p CE is a call to \c TopHandler's completion handler and
/// that the current scope is wrapped in a continuation, replace it with a
/// call to the continuation.
void addHandlerCallToContinuation(const CallExpr *CE) {
assert(TopHandler.getAsHandlerCall(const_cast<CallExpr *>(CE)) == CE &&
"addHandlerCallToContinuation must be used with a call to the "
"TopHandler's completion handler");
assert(Scopes.back().isWrappedInContination());
ArrayRef<Expr *> Args;
StringRef ResumeArgumentLabel;
switch (TopHandler.getHandlerType()) {
case HandlerType::PARAMS: {
auto Exprs = TopHandler.extractResultArgs(CE);
Args = Exprs.args();
if (!Exprs.isError()) {
ResumeArgumentLabel = "returning";
} else {
ResumeArgumentLabel = "throwing";
}
break;
}
case HandlerType::RESULT: {
Args = callArgs(CE).ref();
ResumeArgumentLabel = "with";
break;
}
case HandlerType::INVALID:
llvm_unreachable("Invalid top handler");
}
Identifier ContName = Scopes.back().ContinuationName;
OS << ContName << tok::period << "resume" << tok::l_paren
<< ResumeArgumentLabel << tok::colon << ' ';
unsigned I = 0;
addTupleOf(Args, OS, [&](Expr *Elt) {
// Can't just add the range as we need to perform replacements
convertNode(Elt, /*StartOverride=*/CE->getArgumentLabelLoc(I),
/*ConvertCalls=*/false);
I++;
});
OS << tok::r_paren;
}
/// From the given expression \p E, which is an argument to a function call, /// From the given expression \p E, which is an argument to a function call,
/// extract the passed closure if there is one. Otherwise return \c nullptr. /// extract the passed closure if there is one. Otherwise return \c nullptr.
ClosureExpr *extractCallback(Expr *E) { ClosureExpr *extractCallback(Expr *E) {
@@ -6729,7 +6907,7 @@ private:
StringRef ResultName; StringRef ResultName;
if (!HandlerDesc.willAsyncReturnVoid()) { if (!HandlerDesc.willAsyncReturnVoid()) {
Identifier Unique = createUniqueName("result"); Identifier Unique = createUniqueName("result");
ScopedNames.back().insert(Unique); Scopes.back().Names.insert(Unique);
ResultName = Unique.str(); ResultName = Unique.str();
OS << tok::kw_let << " " << ResultName; OS << tok::kw_let << " " << ResultName;
@@ -6999,7 +7177,7 @@ private:
Identifier createUniqueName(StringRef Name) { Identifier createUniqueName(StringRef Name) {
Identifier Ident = getASTContext().getIdentifier(Name); Identifier Ident = getASTContext().getIdentifier(Name);
auto &CurrentNames = ScopedNames.back(); auto &CurrentNames = Scopes.back().Names;
if (CurrentNames.count(Ident)) { if (CurrentNames.count(Ident)) {
// Add a number to the end of the name until it's unique given the current // Add a number to the end of the name until it's unique given the current
// names in scope. // names in scope.
@@ -7018,7 +7196,7 @@ private:
/// Create a unique name for the variable declared by \p D that doesn't /// Create a unique name for the variable declared by \p D that doesn't
/// clash with any other names in scope, using \p BoundName as the base name /// clash with any other names in scope, using \p BoundName as the base name
/// if not empty and the name of \p D otherwise. Adds this name to both /// if not empty and the name of \p D otherwise. Adds this name to both
/// \c Names and the current scope's names (\c ScopedNames). /// \c Names and the current scope's names (\c Scopes.Names).
Identifier assignUniqueName(const Decl *D, StringRef BoundName) { Identifier assignUniqueName(const Decl *D, StringRef BoundName) {
Identifier Ident; Identifier Ident;
if (BoundName.empty()) { if (BoundName.empty()) {
@@ -7037,7 +7215,7 @@ private:
} }
Names.try_emplace(D, Ident); Names.try_emplace(D, Ident);
ScopedNames.back().insert(Ident); Scopes.back().Names.insert(Ident);
return Ident; return Ident;
} }
@@ -7051,11 +7229,18 @@ private:
} }
void addNewScope(const llvm::DenseSet<const Decl *> &Decls) { void addNewScope(const llvm::DenseSet<const Decl *> &Decls) {
ScopedNames.push_back({}); if (Scopes.empty()) {
for (auto DeclAndNumRefs : Decls) { Scopes.emplace_back(/*ContinuationName=*/Identifier());
auto Name = getDeclName(DeclAndNumRefs); } else {
// If the parent scope is nested in a continuation, the new one is also.
// Carry over the continuation name.
Identifier PreviousContinuationName = Scopes.back().ContinuationName;
Scopes.emplace_back(PreviousContinuationName);
}
for (auto D : Decls) {
auto Name = getDeclName(D);
if (!Name.empty()) if (!Name.empty())
ScopedNames.back().insert(Name); Scopes.back().Names.insert(Name);
} }
} }
@@ -7242,13 +7427,17 @@ private:
void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc) { void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc) {
// Type or (Type1, Type2, ...) // Type or (Type1, Type2, ...)
SmallVector<LabeledReturnType, 2> Scratch; SmallVector<LabeledReturnType, 2> Scratch;
addTupleOf(HandlerDesc.getAsyncReturnTypes(Scratch), OS, auto ReturnTypes = HandlerDesc.getAsyncReturnTypes(Scratch);
[&](LabeledReturnType LabelAndType) { if (ReturnTypes.empty()) {
if (!LabelAndType.Label.empty()) { OS << "Void";
OS << LabelAndType.Label << tok::colon << " "; } else {
} addTupleOf(ReturnTypes, OS, [&](LabeledReturnType LabelAndType) {
LabelAndType.Ty->print(OS); if (!LabelAndType.Label.empty()) {
}); OS << LabelAndType.Label << tok::colon << " ";
}
LabelAndType.Ty->print(OS);
});
}
} }
/// If \p FD is generic, adds a type annotation with the return type of the /// If \p FD is generic, adds a type annotation with the return type of the

View File

@@ -129,8 +129,8 @@ func asyncResNewErr(arg: String, _ completion: (Result<String, Error>) -> Void)
// ASYNC-ERR-NEXT: } // ASYNC-ERR-NEXT: }
// ASYNC-ERR-NEXT: } // ASYNC-ERR-NEXT: }
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=ASYNC-UNHANDLED %s // RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CALL-NON-ASYNC-IN-ASYNC %s
func asyncUnhandledCompletion(_ completion: (String) -> Void) { func callNonAsyncInAsync(_ completion: (String) -> Void) {
simple { str in simple { str in
let success = run { let success = run {
completion(str) completion(str)
@@ -141,19 +141,21 @@ func asyncUnhandledCompletion(_ completion: (String) -> Void) {
} }
} }
} }
// ASYNC-UNHANDLED: func asyncUnhandledCompletion() async -> String { // CALL-NON-ASYNC-IN-ASYNC: func callNonAsyncInAsync() async -> String {
// ASYNC-UNHANDLED-NEXT: let str = await simple() // CALL-NON-ASYNC-IN-ASYNC-NEXT: let str = await simple()
// ASYNC-UNHANDLED-NEXT: let success = run { // CALL-NON-ASYNC-IN-ASYNC-NEXT: return await withCheckedContinuation { continuation in
// ASYNC-UNHANDLED-NEXT: <#completion#>(str) // CALL-NON-ASYNC-IN-ASYNC-NEXT: let success = run {
// ASYNC-UNHANDLED-NEXT: {{^}} return true{{$}} // CALL-NON-ASYNC-IN-ASYNC-NEXT: continuation.resume(returning: str)
// ASYNC-UNHANDLED-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-NEXT: {{^}} return true{{$}}
// ASYNC-UNHANDLED-NEXT: if !success { // CALL-NON-ASYNC-IN-ASYNC-NEXT: }
// ASYNC-UNHANDLED-NEXT: {{^}} return "bad"{{$}} // CALL-NON-ASYNC-IN-ASYNC-NEXT: if !success {
// ASYNC-UNHANDLED-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-NEXT: continuation.resume(returning: "bad")
// ASYNC-UNHANDLED-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-NEXT: }
// CALL-NON-ASYNC-IN-ASYNC-NEXT: }
// CALL-NON-ASYNC-IN-ASYNC-NEXT: }
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=ASYNC-UNHANDLED-COMMENT %s // RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CALL-NON-ASYNC-IN-ASYNC-COMMENT %s
func asyncUnhandledCommentedCompletion(_ completion: (String) -> Void) { func callNonAsyncInAsyncComment(_ completion: (String) -> Void) {
// a // a
simple { str in // b simple { str in // b
// c // c
@@ -174,28 +176,30 @@ func asyncUnhandledCommentedCompletion(_ completion: (String) -> Void) {
} }
// k // k
} }
// ASYNC-UNHANDLED-COMMENT: func asyncUnhandledCommentedCompletion() async -> String { // CALL-NON-ASYNC-IN-ASYNC-COMMENT: func callNonAsyncInAsyncComment() async -> String {
// ASYNC-UNHANDLED-COMMENT-NEXT: // a // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // a
// ASYNC-UNHANDLED-COMMENT-NEXT: let str = await simple() // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: let str = await simple()
// ASYNC-UNHANDLED-COMMENT-NEXT: // b // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // b
// ASYNC-UNHANDLED-COMMENT-NEXT: // c // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // c
// ASYNC-UNHANDLED-COMMENT-NEXT: let success = run { // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: return await withCheckedContinuation { continuation in
// ASYNC-UNHANDLED-COMMENT-NEXT: // d // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: let success = run {
// ASYNC-UNHANDLED-COMMENT-NEXT: <#completion#>(str) // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // d
// ASYNC-UNHANDLED-COMMENT-NEXT: // e // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: continuation.resume(returning: str)
// ASYNC-UNHANDLED-COMMENT-NEXT: {{^}} return true{{$}} // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // e
// ASYNC-UNHANDLED-COMMENT-NEXT: // f // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: {{^}} return true{{$}}
// ASYNC-UNHANDLED-COMMENT-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // f
// ASYNC-UNHANDLED-COMMENT-NEXT: // g // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
// ASYNC-UNHANDLED-COMMENT-NEXT: if !success { // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // g
// ASYNC-UNHANDLED-COMMENT-NEXT: // h // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: if !success {
// ASYNC-UNHANDLED-COMMENT-NEXT: {{^}} return "bad"{{$}} // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // h
// ASYNC-UNHANDLED-COMMENT-NEXT: // i // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: continuation.resume(returning: "bad")
// ASYNC-UNHANDLED-COMMENT-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // i
// ASYNC-UNHANDLED-COMMENT-NEXT: // j // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
// ASYNC-UNHANDLED-COMMENT-NEXT: {{ }} // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // j
// ASYNC-UNHANDLED-COMMENT-NEXT: // k // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: {{ }}
// ASYNC-UNHANDLED-COMMENT-NEXT: } // CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // k
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix VOID-AND-ERROR-HANDLER %s // RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix VOID-AND-ERROR-HANDLER %s
func voidAndErrorCompletion(completion: (Void?, Error?) -> Void) { func voidAndErrorCompletion(completion: (Void?, Error?) -> Void) {
@@ -289,7 +293,9 @@ func testReturnHandling3(_ completion: (String?, Error?) -> Void) {
return (completion("", nil)) return (completion("", nil))
} }
// RETURN-HANDLING3: func testReturnHandling3() async throws -> String { // RETURN-HANDLING3: func testReturnHandling3() async throws -> String {
// RETURN-HANDLING3-NEXT: {{^}} return (<#completion#>("", nil)){{$}} // RETURN-HANDLING3-NEXT: return try await withCheckedThrowingContinuation { continuation in
// RETURN-HANDLING3-NEXT: (continuation.resume(returning: ""))
// RETURN-HANDLING3-NEXT: }
// RETURN-HANDLING3-NEXT: } // RETURN-HANDLING3-NEXT: }
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=RDAR78693050 %s // RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=RDAR78693050 %s

View File

@@ -0,0 +1,417 @@
// RUN: %empty-directory(%t)
func withAsyncAlternative(completionHandler: (Int) -> Void) {}
func withAsyncAlternative() async -> Int { return 42 }
func withAsyncThrowingAlternative(completionHandler: (Int?, Error?) -> Void) {}
func withAsyncThrowingAlternative() async throws -> Int { return 42 }
func withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName(closure: (Int) -> Void) {}
func withoutAsyncAlternativeBecauseOfReturnValue(completionHandler: (Int) -> Void) -> Bool { return true }
func withoutAsyncAlternativeThrowing(closure: (Int?, Error?) -> Void) {}
func asyncVoidWithoutAlternative(completionHandler2: () -> Void) {}
func resultWithoutAlternative(completionHandler2: (Result<Int, Error>) -> Void) {}
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CREATE-CONTINUATION %s
func testCreateContinuation(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
}
// CREATE-CONTINUATION: func testCreateContinuation() async -> Int {
// CREATE-CONTINUATION-NEXT: return await withCheckedContinuation { continuation in
// CREATE-CONTINUATION-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// CREATE-CONTINUATION-NEXT: continuation.resume(returning: $0)
// CREATE-CONTINUATION-NEXT: }
// CREATE-CONTINUATION-NEXT: }
// CREATE-CONTINUATION-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CREATE-CONTINUATION-BECAUSE-RETURN-VALUE %s
func testCreateContinuationBecauseOfReturnValue(completionHandler: (Int) -> Void) {
_ = withoutAsyncAlternativeBecauseOfReturnValue {
completionHandler($0)
}
}
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE: func testCreateContinuationBecauseOfReturnValue() async -> Int {
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: return await withCheckedContinuation { continuation in
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: _ = withoutAsyncAlternativeBecauseOfReturnValue {
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: continuation.resume(returning: $0)
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: }
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: }
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2 %s
func testCreateContinuationBecauseOfReturnValue2(completionHandler: (Int) -> Void) {
let x = withoutAsyncAlternativeBecauseOfReturnValue {
completionHandler($0)
}
print(x)
}
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2: func testCreateContinuationBecauseOfReturnValue2() async -> Int {
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: return await withCheckedContinuation { continuation in
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: let x = withoutAsyncAlternativeBecauseOfReturnValue {
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: continuation.resume(returning: $0)
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: }
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: print(x)
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: }
// CREATE-CONTINUATION-BECAUSE-RETURN-VALUE-2-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CONTINUATION-IN-NESTED-EXPRESSION %s
func testCompletionHandlerCallInNestedExpression(completionHandler: (Int) -> Void) {
print(withoutAsyncAlternativeBecauseOfReturnValue {
completionHandler($0)
})
}
// CONTINUATION-IN-NESTED-EXPRESSION: func testCompletionHandlerCallInNestedExpression() async -> Int {
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: return await withCheckedContinuation { continuation in
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: print(withoutAsyncAlternativeBecauseOfReturnValue {
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: continuation.resume(returning: $0)
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: })
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: }
// CONTINUATION-IN-NESTED-EXPRESSION-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=THROWING-CONTINUATION %s
func testThrowingContinuation(completionHandler: (Int?, Error?) -> Void) {
withoutAsyncAlternativeThrowing { (theValue, theError) in
if let theError = theError {
completionHandler(nil, theError)
} else {
completionHandler(theValue!, nil)
}
}
}
// THROWING-CONTINUATION: func testThrowingContinuation() async throws -> Int {
// THROWING-CONTINUATION-NEXT: return try await withCheckedThrowingContinuation { continuation in
// THROWING-CONTINUATION-NEXT: withoutAsyncAlternativeThrowing { (theValue, theError) in
// THROWING-CONTINUATION-NEXT: if let theError = theError {
// THROWING-CONTINUATION-NEXT: continuation.resume(throwing: theError)
// THROWING-CONTINUATION-NEXT: } else {
// THROWING-CONTINUATION-NEXT: continuation.resume(returning: theValue!)
// THROWING-CONTINUATION-NEXT: }
// THROWING-CONTINUATION-NEXT: }
// THROWING-CONTINUATION-NEXT: }
// We can't relay both the result and the error through the continuation. Converting the following results in a compiler error complaining that theError (of type Error?) can't be passed to `continuation.resume(throwing)`.
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT %s
func testThrowingContinuationRelayingErrorAndResult(completionHandler: (Int?, Error?) -> Void) {
withoutAsyncAlternativeThrowing { (theValue, theError) in
completionHandler(theValue, theError)
}
}
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT: func testThrowingContinuationRelayingErrorAndResult() async throws -> Int {
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: return try await withCheckedThrowingContinuation { continuation in
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: withoutAsyncAlternativeThrowing { (theValue, theError) in
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: continuation.resume(throwing: theError)
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: }
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: }
// THROWING-CONTINUATION-RELAYING-ERROR-AND-RESULT-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=PREVIOUS-COMPLETION-HANDLER-CALL %s
func testPreviousCompletionHandlerCall(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
print($0)
}
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
}
// PREVIOUS-COMPLETION-HANDLER-CALL: func testPreviousCompletionHandlerCall() async -> Int {
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: print($0)
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: }
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: return await withCheckedContinuation { continuation in
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: continuation.resume(returning: $0)
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: }
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: }
// PREVIOUS-COMPLETION-HANDLER-CALL-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=PREVIOUS-ASYNC-CALL %s
func testPreviousAsyncCall(completionHandler: (Int) -> Void) {
withAsyncAlternative { message in
print(message)
}
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
}
// PREVIOUS-ASYNC-CALL: func testPreviousAsyncCall() async -> Int {
// PREVIOUS-ASYNC-CALL-NEXT: let message = await withAsyncAlternative()
// PREVIOUS-ASYNC-CALL-NEXT: print(message)
// PREVIOUS-ASYNC-CALL-NEXT: return await withCheckedContinuation { continuation in
// PREVIOUS-ASYNC-CALL-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// PREVIOUS-ASYNC-CALL-NEXT: continuation.resume(returning: $0)
// PREVIOUS-ASYNC-CALL-NEXT: }
// PREVIOUS-ASYNC-CALL-NEXT: }
// PREVIOUS-ASYNC-CALL-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=IN-IF-ELSE %s
func testInIfElse(completionHandler: (Int) -> Void) {
if true {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
} else {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
}
}
// IN-IF-ELSE: func testInIfElse() async -> Int {
// IN-IF-ELSE-NEXT: if true {
// IN-IF-ELSE-NEXT: return await withCheckedContinuation { continuation in
// IN-IF-ELSE-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// IN-IF-ELSE-NEXT: continuation.resume(returning: $0)
// IN-IF-ELSE-NEXT: }
// IN-IF-ELSE-NEXT: }
// IN-IF-ELSE-NEXT: } else {
// IN-IF-ELSE-NEXT: return await withCheckedContinuation { continuation in
// IN-IF-ELSE-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// IN-IF-ELSE-NEXT: continuation.resume(returning: $0)
// IN-IF-ELSE-NEXT: }
// IN-IF-ELSE-NEXT: }
// IN-IF-ELSE-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=ASYNC-AFTER-CONTINUATION %s
func testAsyncAfterContinuation(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
completionHandler($0)
}
withAsyncAlternative {
print($0)
}
}
// ASYNC-AFTER-CONTINUATION: func testAsyncAfterContinuation() async -> Int {
// ASYNC-AFTER-CONTINUATION-NEXT: return await withCheckedContinuation { continuation in
// ASYNC-AFTER-CONTINUATION-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName {
// ASYNC-AFTER-CONTINUATION-NEXT: continuation.resume(returning: $0)
// ASYNC-AFTER-CONTINUATION-NEXT: }
// ASYNC-AFTER-CONTINUATION-NEXT: withAsyncAlternative {
// ASYNC-AFTER-CONTINUATION-NEXT: print($0)
// ASYNC-AFTER-CONTINUATION-NEXT: }
// ASYNC-AFTER-CONTINUATION-NEXT: }
// ASYNC-AFTER-CONTINUATION-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC %s
func testWithoutAsyncAlternativeNestedInWithoutAsyncAlternative(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { firstResult in
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { secondResult in
completionHandler(firstResult + secondResult)
}
}
}
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC: func testWithoutAsyncAlternativeNestedInWithoutAsyncAlternative() async -> Int {
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: return await withCheckedContinuation { continuation in
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { firstResult in
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { secondResult in
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: continuation.resume(returning: firstResult + secondResult)
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// WITHOUT-ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=WITHOUT-ASYNC-NESTED-IN-ASYNC %s
func testWithoutAsyncAlternativeNestedInAsyncAlternative(completionHandler: (Int) -> Void) {
withAsyncAlternative { firstResult in
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { secondResult in
completionHandler(firstResult + secondResult)
}
}
}
// WITHOUT-ASYNC-NESTED-IN-ASYNC: func testWithoutAsyncAlternativeNestedInAsyncAlternative() async -> Int {
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: let firstResult = await withAsyncAlternative()
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: return await withCheckedContinuation { continuation in
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { secondResult in
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: continuation.resume(returning: firstResult + secondResult)
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: }
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: }
// WITHOUT-ASYNC-NESTED-IN-ASYNC-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=ASYNC-NESTED-IN-WITHOUT-ASYNC %s
func testAsyncAlternativeNestedInWithoutAsyncAlternative(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { firstResult in
withAsyncAlternative { secondResult in
completionHandler(firstResult + secondResult)
}
}
}
// ASYNC-NESTED-IN-WITHOUT-ASYNC: func testAsyncAlternativeNestedInWithoutAsyncAlternative() async -> Int {
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: return await withCheckedContinuation { continuation in
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { firstResult in
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: withAsyncAlternative { secondResult in
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: continuation.resume(returning: firstResult + secondResult)
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// ASYNC-NESTED-IN-WITHOUT-ASYNC-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=SHADOW-CONT-NAME %s
func testShadowContName(completionHandler: (Int) -> Void) {
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { continuation in
completionHandler(continuation)
}
}
// SHADOW-CONT-NAME: func testShadowContName() async -> Int {
// SHADOW-CONT-NAME-NEXT: return await withCheckedContinuation { continuation in
// SHADOW-CONT-NAME-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { continuation1 in
// SHADOW-CONT-NAME-NEXT: continuation.resume(returning: continuation1)
// SHADOW-CONT-NAME-NEXT: }
// SHADOW-CONT-NAME-NEXT: }
// SHADOW-CONT-NAME-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=SHADOW-CONT-NAME-2 %s
func testShadowContName2(completionHandler: (Int) -> Void) {
let continuation = 3
withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { result in
completionHandler(result + continuation)
}
}
// SHADOW-CONT-NAME-2: func testShadowContName2() async -> Int {
// SHADOW-CONT-NAME-2-NEXT: let continuation = 3
// SHADOW-CONT-NAME-2-NEXT: return await withCheckedContinuation { continuation1 in
// SHADOW-CONT-NAME-2-NEXT: withoutAsyncAlternativeBecauseOfMismatchedCompletionHandlerName { result in
// SHADOW-CONT-NAME-2-NEXT: continuation1.resume(returning: result + continuation)
// SHADOW-CONT-NAME-2-NEXT: }
// SHADOW-CONT-NAME-2-NEXT: }
// SHADOW-CONT-NAME-2-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=VOID-RETURN %s
func testVoidReturnValue(completionHandler: () -> Void) {
asyncVoidWithoutAlternative {
completionHandler()
}
}
// VOID-RETURN: func testVoidReturnValue() async {
// VOID-RETURN-NEXT: return await withCheckedContinuation { continuation in
// VOID-RETURN-NEXT: asyncVoidWithoutAlternative {
// VOID-RETURN-NEXT: continuation.resume(returning: ())
// VOID-RETURN-NEXT: }
// VOID-RETURN-NEXT: }
// VOID-RETURN-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=SIMPLE-RESULT %s
func testSimpleResult(completionHandler: (Result<Int, Error>) -> Void) {
resultWithoutAlternative { result in
completionHandler(result)
}
}
// SIMPLE-RESULT: func testSimpleResult() async throws -> Int {
// SIMPLE-RESULT-NEXT: return try await withCheckedThrowingContinuation { continuation in
// SIMPLE-RESULT-NEXT: resultWithoutAlternative { result in
// SIMPLE-RESULT-NEXT: continuation.resume(with: result)
// SIMPLE-RESULT-NEXT: }
// SIMPLE-RESULT-NEXT: }
// SIMPLE-RESULT-NEXT: }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=RESULT-FROM-VALUE-AND-ERROR %s
func testResultFromValueAndError(completionHandler: (Result<Int, Error>) -> Void) {
withoutAsyncAlternativeThrowing { (value, error) in
if let error = error {
completionHandler(.failure(error))
} else {
completionHandler(.success(value!))
}
}
}
// RESULT-FROM-VALUE-AND-ERROR: func testResultFromValueAndError() async throws -> Int {
// RESULT-FROM-VALUE-AND-ERROR-NEXT: return try await withCheckedThrowingContinuation { continuation in
// RESULT-FROM-VALUE-AND-ERROR-NEXT: withoutAsyncAlternativeThrowing { (value, error) in
// RESULT-FROM-VALUE-AND-ERROR-NEXT: if let error = error {
// RESULT-FROM-VALUE-AND-ERROR-NEXT: continuation.resume(with: .failure(error))
// RESULT-FROM-VALUE-AND-ERROR-NEXT: } else {
// RESULT-FROM-VALUE-AND-ERROR-NEXT: continuation.resume(with: .success(value!))
// RESULT-FROM-VALUE-AND-ERROR-NEXT: }
// RESULT-FROM-VALUE-AND-ERROR-NEXT: }
// RESULT-FROM-VALUE-AND-ERROR-NEXT: }
// RESULT-FROM-VALUE-AND-ERROR-NEXT: }
// Reduced version of https://twitter.com/peterfriese/status/1397835146133479428
class DataTask {
let completionHandler: (String?, Error?) -> Void
init(completionHandler: @escaping (String?, Error?) -> Void) {
self.completionHandler = completionHandler
}
func resume() {
completionHandler("mock result", nil)
}
}
class URLSession {
static let shared = URLSession()
func dataTask(completionHandler: @escaping (String?, Error?) -> Void) -> DataTask {
return DataTask(completionHandler: completionHandler)
}
}
func processURLResult(_ data: String) throws -> Int {
return data.count
}
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=URL-SESSION %s
func testDataTask(_ completion: @escaping (Int?) -> Void) {
let dataTask = URLSession.shared.dataTask { (data, error) in
guard let data = data else {
return // Yes, there is a bug about not calling completion here, but that's copied from the Twitter link above
}
do {
let processed = try processURLResult(data)
completion(processed)
} catch {
completion(nil)
}
}
dataTask.resume()
}
// URL-SESSION: func testDataTask() async -> Int?
// URL-SESSION-NEXT: return await withCheckedContinuation { continuation in
// URL-SESSION-NEXT: let dataTask1 = URLSession.shared.dataTask { (data, error) in
// URL-SESSION-NEXT: guard let data1 = data else {
// URL-SESSION-NEXT: return // Yes, there is a bug about not calling completion here, but that's copied from the Twitter link above
// URL-SESSION-NEXT: }
// URL-SESSION-NEXT: do {
// URL-SESSION-NEXT: let processed = try processURLResult(data1)
// URL-SESSION-NEXT: continuation.resume(returning: processed)
// URL-SESSION-NEXT: } catch {
// URL-SESSION-NEXT: continuation.resume(returning: nil)
// URL-SESSION-NEXT: }
// URL-SESSION-NEXT: }
// URL-SESSION-NEXT: dataTask1.resume()
// URL-SESSION-NEXT: }
// URL-SESSION-NEXT: }
// Reduced version of rdar://79304583
class DispatchQueue {
init() {}
func async(execute work: @escaping @convention(block) () -> Void) {
work()
}
}
func syncComputation() -> Int { return 42 }
// RUN: %refactor-check-compiles -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=DISPATCH %s
func testDispatch(_ completionHandler: @escaping (Int) -> Void) {
let queue = DispatchQueue()
queue.async {
completionHandler(syncComputation())
}
}
// DISPATCH: func testDispatch() async -> Int {
// DISPATCH-NEXT: let queue = DispatchQueue()
// DISPATCH-NEXT: return await withCheckedContinuation { continuation in
// DISPATCH-NEXT: queue.async {
// DISPATCH-NEXT: continuation.resume(returning: syncComputation())
// DISPATCH-NEXT: }
// DISPATCH-NEXT: }
// DISPATCH-NEXT: }