Better handle non-refutable patterns in async transform

Keep track of patterns that bind multiple vars and
print them out when converting an async call. If
the parameter being bound isn't referenced elsewhere,
we'll print the pattern inline as e.g:

```
let ((x, y), z) = await foo()
```

Otherwise, if the parameter is referenced elsewhere
in the block we'll print the pattern out of line,
such as:

```
let (res, z) = await foo()
let (x, y) = res
```

In addition, make sure to print var bindings out
of line if there's also a let binding, e.g:

```
let x = await foo()
var y = x
```

This ensures any mutations to y doesn't affect x.
If there's only a single var binding, we'll print
it inline.

rdar://77802560
This commit is contained in:
Hamish Knight
2021-06-05 18:41:13 +01:00
parent 34402346be
commit f28284dcc3
2 changed files with 722 additions and 50 deletions

View File

@@ -4755,22 +4755,87 @@ public:
/// decls to any patterns they are used in.
class ClassifiedBlock {
NodesToPrint Nodes;
// closure param -> name
llvm::DenseMap<const Decl *, StringRef> BoundNames;
// var (ie. from a let binding) -> closure param
llvm::DenseMap<const Decl *, const Decl *> Aliases;
bool AllLet = true;
// A mapping of closure params to a list of patterns that bind them.
using ParamPatternBindingsMap =
llvm::MapVector<const Decl *, TinyPtrVector<const Pattern *>>;
ParamPatternBindingsMap ParamPatternBindings;
public:
const NodesToPrint &nodesToPrint() const { return Nodes; }
StringRef boundName(const Decl *D) const { return BoundNames.lookup(D); }
/// Attempt to retrieve an existing bound name for a closure parameter, or
/// an empty string if there's no suitable existing binding.
StringRef boundName(const Decl *D) const {
// Adopt the same name as the representative single pattern, if it only
// binds a single var.
if (auto *P = getSinglePatternFor(D)) {
if (P->getSingleVar())
return P->getBoundName().str();
}
return StringRef();
}
const llvm::DenseMap<const Decl *, const Decl *> &aliases() const {
/// Checks whether a closure parameter can be represented by a single pattern
/// that binds it. If the param is only bound by a single pattern, that will
/// be returned. If there's a pattern with a single var that binds it, that
/// will be returned, preferring a 'let' pattern to prefer out of line
/// printing of 'var' patterns.
const Pattern *getSinglePatternFor(const Decl *D) const {
auto Iter = ParamPatternBindings.find(D);
if (Iter == ParamPatternBindings.end())
return nullptr;
const auto &Patterns = Iter->second;
if (Patterns.empty())
return nullptr;
if (Patterns.size() == 1)
return Patterns[0];
// If we have multiple patterns, search for the best single var pattern to
// use, preferring a 'let' binding.
const Pattern *FirstSingleVar = nullptr;
for (auto *P : Patterns) {
if (!P->getSingleVar())
continue;
if (!P->hasAnyMutableBindings())
return P;
if (!FirstSingleVar)
FirstSingleVar = P;
}
return FirstSingleVar;
}
/// Retrieve any bound vars that are effectively aliases of a given closure
/// parameter.
llvm::SmallDenseSet<const Decl *> getAliasesFor(const Decl *D) const {
auto Iter = ParamPatternBindings.find(D);
if (Iter == ParamPatternBindings.end())
return {};
llvm::SmallDenseSet<const Decl *> Aliases;
// The single pattern that we replace the decl with is always an alias.
if (auto *P = getSinglePatternFor(D)) {
if (auto *SingleVar = P->getSingleVar())
Aliases.insert(SingleVar);
}
// Any other let bindings we have are also aliases.
for (auto *P : Iter->second) {
if (auto *SingleVar = P->getSingleVar()) {
if (!P->hasAnyMutableBindings())
Aliases.insert(SingleVar);
}
}
return Aliases;
}
bool allLet() const { return AllLet; }
const ParamPatternBindingsMap &paramPatternBindings() const {
return ParamPatternBindings;
}
void addNodesInBraceStmt(BraceStmt *Brace) {
Nodes.addNodesInBraceStmt(Brace);
@@ -4788,26 +4853,17 @@ public:
void addBinding(const ClassifiedCondition &FromCondition,
DiagnosticEngine &DiagEngine) {
if (!FromCondition.BindPattern)
auto *P = FromCondition.BindPattern;
if (!P)
return;
if (auto *BP =
dyn_cast_or_null<BindingPattern>(FromCondition.BindPattern)) {
if (!BP->isLet())
AllLet = false;
}
StringRef Name = FromCondition.BindPattern->getBoundName().str();
VarDecl *SingleVar = FromCondition.BindPattern->getSingleVar();
if (Name.empty() || !SingleVar)
// Patterns that don't bind anything aren't interesting.
SmallVector<VarDecl *, 2> Vars;
P->collectVariables(Vars);
if (Vars.empty())
return;
auto Res = Aliases.try_emplace(SingleVar, FromCondition.Subject);
assert(Res.second && "Should not have seen this var before");
(void)Res;
// Use whichever name comes first
BoundNames.try_emplace(FromCondition.Subject, Name);
ParamPatternBindings[FromCondition.Subject].push_back(P);
}
void addAllBindings(const ClassifiedCallbackConditions &FromConditions,
@@ -5660,6 +5716,12 @@ class AsyncConverter : private SourceEntityWalker {
// call
bool Hoisting = false;
/// Whether a pattern is currently being converted.
bool ConvertingPattern = false;
/// A mapping of inline patterns to print for closure parameters.
using InlinePatternsToPrint = llvm::DenseMap<const Decl *, const Pattern *>;
public:
/// Convert a function
AsyncConverter(SourceFile *SF, SourceManager &SM,
@@ -6010,6 +6072,37 @@ private:
addRange(LastAddedLoc, Node.getEndLoc(), /*ToEndOfToken=*/true);
}
void convertPattern(const Pattern *P) {
// Only print semantic patterns. This cleans up the output of the transform
// and works around some bogus source locs that can appear with typed
// patterns in if let statements.
P = P->getSemanticsProvidingPattern();
// Set up the start of the pattern as the last loc printed to make sure we
// accurately fill in the gaps as we customize the printing of sub-patterns.
llvm::SaveAndRestore<SourceLoc> RestoreLoc(LastAddedLoc, P->getStartLoc());
llvm::SaveAndRestore<bool> RestoreFlag(ConvertingPattern, true);
walk(const_cast<Pattern *>(P));
addRange(LastAddedLoc, P->getEndLoc(), /*ToEndOfToken*/ true);
}
bool walkToPatternPre(Pattern *P) override {
// If we're not converting a pattern, there's nothing extra to do.
if (!ConvertingPattern)
return true;
// When converting a pattern, don't print the 'let' or 'var' of binding
// subpatterns, as they're illegal when nested in PBDs, and we print a
// top-level one.
if (auto *BP = dyn_cast<BindingPattern>(P)) {
return addCustom(BP->getSourceRange(), [&]() {
convertPattern(BP->getSubPattern());
});
}
return true;
}
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
if (isa<PatternBindingDecl>(D)) {
NestedExprCount++;
@@ -6018,14 +6111,16 @@ private:
// Functions and types already have their names in \c ScopedNames, only
// variables should need to be renamed.
if (isa<VarDecl>(D) && Names.find(D) == Names.end()) {
Identifier Ident = assignUniqueName(D, StringRef());
if (!Ident.empty()) {
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);
addCustom(D->getSourceRange(), [&]() {
OS << Ident.str();
});
}
addCustom(D->getSourceRange(), [&]() {
OS << newNameFor(D);
});
}
// Note we don't walk into any nested local function decls. If we start
@@ -6359,8 +6454,9 @@ private:
if (!HandlerDesc.willAsyncReturnVoid()) {
OS << tok::kw_return << " ";
}
addAwaitCall(CE, ArgList.ref(), ClassifiedBlock(), {}, HandlerDesc,
/*AddDeclarations=*/false);
InlinePatternsToPrint InlinePatterns;
addAwaitCall(CE, ArgList.ref(), ClassifiedBlock(), {}, InlinePatterns,
HandlerDesc, /*AddDeclarations*/ false);
return;
}
// We are not removing the completion handler, so we can call it once the
@@ -6377,8 +6473,10 @@ private:
.str();
addHoistedNamedCallback(
CalledFunc, CompletionHandler, HandlerName, [&] {
InlinePatternsToPrint InlinePatterns;
addAwaitCall(CE, ArgList.ref(), ClassifiedBlock(), {},
HandlerDesc, /*AddDeclarations=*/false);
InlinePatterns, HandlerDesc,
/*AddDeclarations*/ false);
});
return;
}
@@ -6442,14 +6540,18 @@ private:
return;
DiagEngine.resetHadAnyError();
// Note that we don't print any inline patterns here as we just want
// assignments to the names in the outer scope.
InlinePatternsToPrint InlinePatterns;
// Don't do any unwrapping or placeholder replacement since all params
// are still valid in the fallback case
prepareNames(ClassifiedBlock(), CallbackParams);
prepareNames(ClassifiedBlock(), CallbackParams, InlinePatterns);
addFallbackVars(CallbackParams, Blocks);
addDo();
addAwaitCall(CE, ArgList.ref(), Blocks.SuccessBlock, SuccessParams,
HandlerDesc, /*AddDeclarations*/ false);
InlinePatterns, HandlerDesc, /*AddDeclarations*/ false);
addFallbackCatch(ErrParam);
OS << "\n";
convertNodes(NodesToPrint::inBraceStmt(CallbackBody));
@@ -6490,19 +6592,26 @@ private:
addDo();
}
prepareNames(Blocks.SuccessBlock, SuccessParams);
auto InlinePatterns =
getInlinePatternsToPrint(Blocks.SuccessBlock, SuccessParams, Callback);
prepareNames(Blocks.SuccessBlock, SuccessParams, InlinePatterns);
preparePlaceholdersAndUnwraps(HandlerDesc, SuccessParams, ErrParam,
/*Success=*/true);
addAwaitCall(CE, ArgList.ref(), Blocks.SuccessBlock, SuccessParams,
HandlerDesc, /*AddDeclarations=*/true);
InlinePatterns, HandlerDesc, /*AddDeclarations*/ true);
printOutOfLineBindingPatterns(Blocks.SuccessBlock, InlinePatterns);
convertNodes(Blocks.SuccessBlock.nodesToPrint());
clearNames(SuccessParams);
if (RequireDo) {
// Always use the ErrParam name if none is bound
// We don't use inline patterns for the error path.
InlinePatternsToPrint ErrInlinePatterns;
// Always use the ErrParam name if none is bound.
prepareNames(Blocks.ErrorBlock, llvm::makeArrayRef(ErrParam),
HandlerDesc.Type != HandlerType::RESULT);
ErrInlinePatterns, HandlerDesc.Type != HandlerType::RESULT);
preparePlaceholdersAndUnwraps(HandlerDesc, SuccessParams, ErrParam,
/*Success=*/false);
@@ -6563,15 +6672,123 @@ private:
}
}
/// Checks whether a binding pattern for a given decl can be printed inline in
/// an await call, e.g 'let ((x, y), z) = await foo()', where '(x, y)' is the
/// inline pattern.
const Pattern *
bindingPatternToPrintInline(const Decl *D, const ClassifiedBlock &Block,
const ClosureExpr *CallbackClosure) {
// Only currently done for callback closures.
if (!CallbackClosure)
return nullptr;
// If we can reduce the pattern bindings down to a single pattern, we may
// be able to print it inline.
auto *P = Block.getSinglePatternFor(D);
if (!P)
return nullptr;
// Patterns that bind a single var are always printed inline.
if (P->getSingleVar())
return P;
// If we have a multi-var binding, and the decl being bound is referenced
// elsewhere in the block, we cannot print the pattern immediately in the
// await call. Instead, we'll print it out of line.
auto *Decls = ScopedDecls.getReferencedDecls(CallbackClosure->getBody());
assert(Decls);
auto NumRefs = Decls->lookup(D);
return NumRefs == 1 ? P : nullptr;
}
/// Retrieve a map of patterns to print inline for an array of param decls.
InlinePatternsToPrint
getInlinePatternsToPrint(const ClassifiedBlock &Block,
ArrayRef<const ParamDecl *> Params,
const ClosureExpr *CallbackClosure) {
InlinePatternsToPrint Patterns;
for (auto *Param : Params) {
if (auto *P = bindingPatternToPrintInline(Param, Block, CallbackClosure))
Patterns[Param] = P;
}
return Patterns;
}
/// Print any out of line binding patterns that could not be printed as inline
/// patterns. These typically appear directly after an await call, e.g:
/// \code
/// let x = await foo()
/// let (y, z) = x
/// \endcode
void
printOutOfLineBindingPatterns(const ClassifiedBlock &Block,
const InlinePatternsToPrint &InlinePatterns) {
for (auto &Entry : Block.paramPatternBindings()) {
auto *D = Entry.first;
auto Aliases = Block.getAliasesFor(D);
for (auto *P : Entry.second) {
// If we already printed this as an inline pattern, there's nothing else
// to do.
if (InlinePatterns.lookup(D) == P)
continue;
// If this is an alias binding, it can be elided.
if (auto *SingleVar = P->getSingleVar()) {
if (Aliases.contains(SingleVar))
continue;
}
auto HasMutable = P->hasAnyMutableBindings();
OS << "\n" << (HasMutable ? tok::kw_var : tok::kw_let) << " ";
convertPattern(P);
OS << " = ";
OS << newNameFor(D);
}
}
}
/// Prints an \c await call to an \c async function, binding any return values
/// into variables.
///
/// \param CE The call expr to convert.
/// \param Args The arguments of the call expr.
/// \param SuccessBlock The nodes present in the success block following the
/// call.
/// \param SuccessParams The success parameters, which will be printed as
/// return values.
/// \param InlinePatterns A map of patterns that can be printed inline for
/// a given param.
/// \param HandlerDesc A description of the completion handler.
/// \param AddDeclarations Whether or not to add \c let or \c var keywords to
/// the return value bindings.
void addAwaitCall(const CallExpr *CE, ArrayRef<Expr *> Args,
const ClassifiedBlock &SuccessBlock,
ArrayRef<const ParamDecl *> SuccessParams,
const InlinePatternsToPrint &InlinePatterns,
const AsyncHandlerDesc &HandlerDesc, bool AddDeclarations) {
// Print the bindings to match the completion handler success parameters,
// making sure to omit in the case of a Void return.
if (!SuccessParams.empty() && !HandlerDesc.willAsyncReturnVoid()) {
auto AllLet = true;
// Gather the items to print for the variable bindings. This can either be
// a param decl, or a pattern that binds it.
using DeclOrPattern = llvm::PointerUnion<const Decl *, const Pattern *>;
SmallVector<DeclOrPattern, 4> ToPrint;
for (auto *Param : SuccessParams) {
// Check if we have an inline pattern to print.
if (auto *P = InlinePatterns.lookup(Param)) {
if (P->hasAnyMutableBindings())
AllLet = false;
ToPrint.push_back(P);
continue;
}
ToPrint.push_back(Param);
}
if (AddDeclarations) {
if (SuccessBlock.allLet()) {
if (AllLet) {
OS << tok::kw_let;
} else {
OS << tok::kw_var;
@@ -6579,8 +6796,13 @@ private:
OS << " ";
}
// 'res =' or '(res1, res2, ...) ='
addTupleOf(SuccessParams, OS,
[&](auto &Param) { OS << newNameFor(Param); });
addTupleOf(ToPrint, OS, [&](DeclOrPattern Elt) {
if (auto *P = Elt.dyn_cast<const Pattern *>()) {
convertPattern(P);
return;
}
OS << newNameFor(Elt.get<const Decl *>());
});
OS << " " << tok::equal << " ";
}
@@ -6675,17 +6897,26 @@ private:
/// name will be added.
void prepareNames(const ClassifiedBlock &Block,
ArrayRef<const ParamDecl *> Params,
const InlinePatternsToPrint &InlinePatterns,
bool AddIfMissing = true) {
for (auto *PD : Params) {
StringRef Name = Block.boundName(PD);
if (!Name.empty() || AddIfMissing)
assignUniqueName(PD, Name);
// If this param is to be replaced by a pattern that binds multiple
// separate vars, it's not actually going to be added to the scope, and
// therefore doesn't need naming. This avoids needing to rename a var with
// the same name later on in the scope, as it's not actually clashing.
if (auto *P = InlinePatterns.lookup(PD)) {
if (!P->getSingleVar())
continue;
}
auto Name = Block.boundName(PD);
if (Name.empty() && !AddIfMissing)
continue;
for (auto &Entry : Block.aliases()) {
auto It = Names.find(Entry.second);
assert(It != Names.end() && "Param should already have an entry");
Names[Entry.first] = It->second;
auto Ident = assignUniqueName(PD, Name);
// Also propagate the name to any aliases.
for (auto *Alias : Block.getAliasesFor(PD))
Names[Alias] = Ident;
}
}

View File

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