mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[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:
@@ -4183,6 +4183,8 @@ struct AsyncHandlerDesc {
|
||||
}
|
||||
}
|
||||
|
||||
HandlerType getHandlerType() const { return Type; }
|
||||
|
||||
/// Get the type of the completion handler.
|
||||
swift::Type getType() const {
|
||||
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.
|
||||
///
|
||||
/// 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,
|
||||
/// with any unhandled decls wrapped in placeholders instead.
|
||||
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;
|
||||
SourceManager &SM;
|
||||
DiagnosticEngine &DiagEngine;
|
||||
@@ -5755,9 +5799,12 @@ class AsyncConverter : private SourceEntityWalker {
|
||||
// declarations of old completion handler parametes, as well as the
|
||||
// replacement for other hoisted declarations and their references
|
||||
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.
|
||||
llvm::SmallVector<llvm::DenseSet<DeclBaseName>, 4> ScopedNames;
|
||||
|
||||
/// The scopes (containing all name decls and whether the scope is wrapped in
|
||||
/// 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
|
||||
// without first being declared. These are used to fill the \c ScopeNames
|
||||
// map on entering that scope.
|
||||
@@ -6149,6 +6196,58 @@ private:
|
||||
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 {
|
||||
// If we're not converting a pattern, there's nothing extra to do.
|
||||
if (!ConvertingPattern)
|
||||
@@ -6167,18 +6266,21 @@ private:
|
||||
|
||||
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
|
||||
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++;
|
||||
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.
|
||||
if (isa<VarDecl>(D)) {
|
||||
// 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.
|
||||
if (Names.find(D) == Names.end()) {
|
||||
auto Ident = assignUniqueName(D, StringRef());
|
||||
ScopedNames.back().insert(Ident);
|
||||
Scopes.back().Names.insert(Ident);
|
||||
}
|
||||
addCustom(D->getSourceRange(), [&]() {
|
||||
OS << newNameFor(D);
|
||||
@@ -6239,11 +6341,20 @@ private:
|
||||
return addCustom(E->getSourceRange(),
|
||||
[&]() { OS << newNameFor(D, true); });
|
||||
}
|
||||
} else if (NestedExprCount == 0) {
|
||||
if (CallExpr *CE = TopHandler.getAsHandlerCall(E))
|
||||
} else 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); });
|
||||
|
||||
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
|
||||
// to have the @completionHandlerAsync attribute or a completion-like
|
||||
// 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++;
|
||||
return true;
|
||||
}
|
||||
@@ -6319,18 +6434,37 @@ private:
|
||||
}
|
||||
|
||||
bool walkToStmtPost(Stmt *S) override {
|
||||
if (startsNewScope(S))
|
||||
ScopedNames.pop_back();
|
||||
if (startsNewScope(S)) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool addCustom(SourceRange Range, std::function<void()> Custom = {}) {
|
||||
bool addCustom(SourceRange Range, llvm::function_ref<void()> Custom = {}) {
|
||||
addRange(LastAddedLoc, Range.Start);
|
||||
Custom();
|
||||
LastAddedLoc = Lexer::getLocForEndOfToken(SM, Range.End);
|
||||
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) {
|
||||
if (ToEndOfToken) {
|
||||
OS << Lexer::getCharSourceRangeFromSourceRange(SM,
|
||||
@@ -6439,6 +6573,9 @@ private:
|
||||
void addDo() { OS << tok::kw_do << " " << tok::l_brace << "\n"; }
|
||||
|
||||
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);
|
||||
|
||||
bool AddedReturnOrThrow = true;
|
||||
@@ -6462,20 +6599,61 @@ private:
|
||||
if (!Args.empty()) {
|
||||
if (AddedReturnOrThrow)
|
||||
OS << " ";
|
||||
if (Args.size() > 1)
|
||||
OS << tok::l_paren;
|
||||
for (size_t I = 0, E = Args.size(); I < E; ++I) {
|
||||
if (I > 0)
|
||||
OS << tok::comma << " ";
|
||||
unsigned I = 0;
|
||||
addTupleOf(Args, OS, [&](Expr *Elt) {
|
||||
// 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);
|
||||
}
|
||||
if (Args.size() > 1)
|
||||
OS << tok::r_paren;
|
||||
I++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// extract the passed closure if there is one. Otherwise return \c nullptr.
|
||||
ClosureExpr *extractCallback(Expr *E) {
|
||||
@@ -6729,7 +6907,7 @@ private:
|
||||
StringRef ResultName;
|
||||
if (!HandlerDesc.willAsyncReturnVoid()) {
|
||||
Identifier Unique = createUniqueName("result");
|
||||
ScopedNames.back().insert(Unique);
|
||||
Scopes.back().Names.insert(Unique);
|
||||
ResultName = Unique.str();
|
||||
|
||||
OS << tok::kw_let << " " << ResultName;
|
||||
@@ -6999,7 +7177,7 @@ private:
|
||||
Identifier createUniqueName(StringRef Name) {
|
||||
Identifier Ident = getASTContext().getIdentifier(Name);
|
||||
|
||||
auto &CurrentNames = ScopedNames.back();
|
||||
auto &CurrentNames = Scopes.back().Names;
|
||||
if (CurrentNames.count(Ident)) {
|
||||
// Add a number to the end of the name until it's unique given the current
|
||||
// names in scope.
|
||||
@@ -7018,7 +7196,7 @@ private:
|
||||
/// 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
|
||||
/// 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 Ident;
|
||||
if (BoundName.empty()) {
|
||||
@@ -7037,7 +7215,7 @@ private:
|
||||
}
|
||||
|
||||
Names.try_emplace(D, Ident);
|
||||
ScopedNames.back().insert(Ident);
|
||||
Scopes.back().Names.insert(Ident);
|
||||
return Ident;
|
||||
}
|
||||
|
||||
@@ -7051,11 +7229,18 @@ private:
|
||||
}
|
||||
|
||||
void addNewScope(const llvm::DenseSet<const Decl *> &Decls) {
|
||||
ScopedNames.push_back({});
|
||||
for (auto DeclAndNumRefs : Decls) {
|
||||
auto Name = getDeclName(DeclAndNumRefs);
|
||||
if (Scopes.empty()) {
|
||||
Scopes.emplace_back(/*ContinuationName=*/Identifier());
|
||||
} 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())
|
||||
ScopedNames.back().insert(Name);
|
||||
Scopes.back().Names.insert(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7242,13 +7427,17 @@ private:
|
||||
void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc) {
|
||||
// Type or (Type1, Type2, ...)
|
||||
SmallVector<LabeledReturnType, 2> Scratch;
|
||||
addTupleOf(HandlerDesc.getAsyncReturnTypes(Scratch), OS,
|
||||
[&](LabeledReturnType LabelAndType) {
|
||||
if (!LabelAndType.Label.empty()) {
|
||||
OS << LabelAndType.Label << tok::colon << " ";
|
||||
}
|
||||
LabelAndType.Ty->print(OS);
|
||||
});
|
||||
auto ReturnTypes = HandlerDesc.getAsyncReturnTypes(Scratch);
|
||||
if (ReturnTypes.empty()) {
|
||||
OS << "Void";
|
||||
} else {
|
||||
addTupleOf(ReturnTypes, OS, [&](LabeledReturnType LabelAndType) {
|
||||
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
|
||||
|
||||
@@ -129,8 +129,8 @@ func asyncResNewErr(arg: String, _ completion: (Result<String, Error>) -> Void)
|
||||
// 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
|
||||
func asyncUnhandledCompletion(_ completion: (String) -> Void) {
|
||||
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=CALL-NON-ASYNC-IN-ASYNC %s
|
||||
func callNonAsyncInAsync(_ completion: (String) -> Void) {
|
||||
simple { str in
|
||||
let success = run {
|
||||
completion(str)
|
||||
@@ -141,19 +141,21 @@ func asyncUnhandledCompletion(_ completion: (String) -> Void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ASYNC-UNHANDLED: func asyncUnhandledCompletion() async -> String {
|
||||
// ASYNC-UNHANDLED-NEXT: let str = await simple()
|
||||
// ASYNC-UNHANDLED-NEXT: let success = run {
|
||||
// ASYNC-UNHANDLED-NEXT: <#completion#>(str)
|
||||
// ASYNC-UNHANDLED-NEXT: {{^}} return true{{$}}
|
||||
// ASYNC-UNHANDLED-NEXT: }
|
||||
// ASYNC-UNHANDLED-NEXT: if !success {
|
||||
// ASYNC-UNHANDLED-NEXT: {{^}} return "bad"{{$}}
|
||||
// ASYNC-UNHANDLED-NEXT: }
|
||||
// ASYNC-UNHANDLED-NEXT: }
|
||||
// CALL-NON-ASYNC-IN-ASYNC: func callNonAsyncInAsync() async -> String {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: let str = await simple()
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: return await withCheckedContinuation { continuation in
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: let success = run {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: continuation.resume(returning: str)
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: {{^}} return true{{$}}
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: }
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: if !success {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-NEXT: continuation.resume(returning: "bad")
|
||||
// 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
|
||||
func asyncUnhandledCommentedCompletion(_ completion: (String) -> Void) {
|
||||
// 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 callNonAsyncInAsyncComment(_ completion: (String) -> Void) {
|
||||
// a
|
||||
simple { str in // b
|
||||
// c
|
||||
@@ -174,28 +176,30 @@ func asyncUnhandledCommentedCompletion(_ completion: (String) -> Void) {
|
||||
}
|
||||
// k
|
||||
}
|
||||
// ASYNC-UNHANDLED-COMMENT: func asyncUnhandledCommentedCompletion() async -> String {
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // a
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: let str = await simple()
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // b
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // c
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: let success = run {
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // d
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: <#completion#>(str)
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // e
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: {{^}} return true{{$}}
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // f
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: }
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // g
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: if !success {
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // h
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: {{^}} return "bad"{{$}}
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // i
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: }
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // j
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: {{ }}
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: // k
|
||||
// ASYNC-UNHANDLED-COMMENT-NEXT: }
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT: func callNonAsyncInAsyncComment() async -> String {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // a
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: let str = await simple()
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // b
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // c
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: return await withCheckedContinuation { continuation in
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: let success = run {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // d
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: continuation.resume(returning: str)
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // e
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: {{^}} return true{{$}}
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // f
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // g
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: if !success {
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // h
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: continuation.resume(returning: "bad")
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // i
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: }
|
||||
// CALL-NON-ASYNC-IN-ASYNC-COMMENT-NEXT: // j
|
||||
// CALL-NON-ASYNC-IN-ASYNC-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
|
||||
func voidAndErrorCompletion(completion: (Void?, Error?) -> Void) {
|
||||
@@ -289,7 +293,9 @@ func testReturnHandling3(_ completion: (String?, Error?) -> Void) {
|
||||
return (completion("", nil))
|
||||
}
|
||||
// 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: }
|
||||
|
||||
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=RDAR78693050 %s
|
||||
|
||||
417
test/refactoring/ConvertAsync/convert_to_continuation.swift
Normal file
417
test/refactoring/ConvertAsync/convert_to_continuation.swift
Normal 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: }
|
||||
Reference in New Issue
Block a user