mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
1388 lines
54 KiB
C++
1388 lines
54 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_REFACTORING_ASYNCREFACTORING_H
|
|
#define SWIFT_REFACTORING_ASYNCREFACTORING_H
|
|
|
|
#include "ContextFinder.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/DiagnosticsRefactoring.h"
|
|
#include "swift/AST/Expr.h"
|
|
#include "swift/AST/ForeignAsyncConvention.h"
|
|
#include "swift/AST/GenericParamList.h"
|
|
#include "swift/AST/ParameterList.h"
|
|
#include "swift/AST/Pattern.h"
|
|
#include "swift/AST/Stmt.h"
|
|
#include "swift/ClangImporter/ClangImporter.h"
|
|
#include "swift/IDE/Utils.h"
|
|
#include "swift/Parse/Lexer.h"
|
|
|
|
namespace swift {
|
|
namespace refactoring {
|
|
namespace asyncrefactorings {
|
|
|
|
using namespace swift::ide;
|
|
|
|
FuncDecl *getUnderlyingFunc(const Expr *Fn);
|
|
|
|
/// Describes the expressions to be kept from a call to the handler in a
|
|
/// function that has (or will have ) and async alternative. Eg.
|
|
/// ```
|
|
/// func toBeAsync(completion: (String?, Error?) -> Void) {
|
|
/// ...
|
|
/// completion("something", nil) // Result = ["something"], IsError = false
|
|
/// ...
|
|
/// completion(nil, MyError.Bad) // Result = [MyError.Bad], IsError = true
|
|
/// }
|
|
class HandlerResult {
|
|
SmallVector<Argument, 2> Args;
|
|
bool IsError = false;
|
|
|
|
public:
|
|
HandlerResult() {}
|
|
|
|
HandlerResult(ArrayRef<Argument> ArgsRef)
|
|
: Args(ArgsRef.begin(), ArgsRef.end()) {}
|
|
|
|
HandlerResult(Argument Arg, bool IsError) : IsError(IsError) {
|
|
Args.push_back(Arg);
|
|
}
|
|
|
|
bool isError() { return IsError; }
|
|
|
|
ArrayRef<Argument> args() { return Args; }
|
|
};
|
|
|
|
/// The type of the handler, ie. whether it takes regular parameters or a
|
|
/// single parameter of `Result` type.
|
|
enum class HandlerType { INVALID, PARAMS, RESULT };
|
|
|
|
/// A single return type of a refactored async function. If the async function
|
|
/// returns a tuple, each element of the tuple (represented by a \c
|
|
/// LabeledReturnType) might have a label, otherwise the \p Label is empty.
|
|
struct LabeledReturnType {
|
|
Identifier Label;
|
|
swift::Type Ty;
|
|
|
|
LabeledReturnType(Identifier Label, swift::Type Ty) : Label(Label), Ty(Ty) {}
|
|
};
|
|
|
|
/// Given a function with an async alternative (or one that *could* have an
|
|
/// async alternative), stores information about the completion handler.
|
|
/// The completion handler can be either a variable (which includes a parameter)
|
|
/// or a function
|
|
struct AsyncHandlerDesc {
|
|
PointerUnion<const VarDecl *, const AbstractFunctionDecl *> Handler = nullptr;
|
|
HandlerType Type = HandlerType::INVALID;
|
|
bool HasError = false;
|
|
|
|
static AsyncHandlerDesc get(const ValueDecl *Handler, bool RequireName);
|
|
|
|
bool isValid() const { return Type != HandlerType::INVALID; }
|
|
|
|
/// Return the declaration of the completion handler as a \c ValueDecl.
|
|
/// In practice, the handler will always be a \c VarDecl or \c
|
|
/// AbstractFunctionDecl.
|
|
/// \c getNameStr and \c getType provide access functions that are available
|
|
/// for both variables and functions, but not on \c ValueDecls.
|
|
const ValueDecl *getHandler() const;
|
|
|
|
/// Return the name of the completion handler. If it is a variable, the
|
|
/// variable name, if it's a function, the function base name.
|
|
StringRef getNameStr() const;
|
|
|
|
HandlerType getHandlerType() const { return Type; }
|
|
|
|
/// Get the type of the completion handler.
|
|
swift::Type getType() const;
|
|
|
|
ArrayRef<AnyFunctionType::Param> params() const;
|
|
|
|
/// Retrieve the parameters relevant to a successful return from the
|
|
/// completion handler. This drops the Error parameter if present.
|
|
ArrayRef<AnyFunctionType::Param> getSuccessParams() const;
|
|
|
|
/// If the completion handler has an Error parameter, return it.
|
|
std::optional<AnyFunctionType::Param> getErrorParam() const;
|
|
|
|
/// Get the type of the error that will be thrown by the \c async method or \c
|
|
/// None if the completion handler doesn't accept an error parameter.
|
|
/// This may be more specialized than the generic 'Error' type if the
|
|
/// completion handler of the converted function takes a more specialized
|
|
/// error type.
|
|
std::optional<swift::Type> getErrorType() const;
|
|
|
|
/// The `CallExpr` if the given node is a call to the `Handler`
|
|
CallExpr *getAsHandlerCall(ASTNode Node) const;
|
|
|
|
/// Returns \c true if the call to the completion handler contains possibly
|
|
/// non-nil values for both the success and error parameters, e.g.
|
|
/// \code
|
|
/// completion(result, error)
|
|
/// \endcode
|
|
/// This can only happen if the completion handler is a params handler.
|
|
bool isAmbiguousCallToParamHandler(const CallExpr *CE) const;
|
|
|
|
/// Given a call to the `Handler`, extract the expressions to be returned or
|
|
/// thrown, taking care to remove the `.success`/`.failure` if it's a
|
|
/// `RESULT` handler type.
|
|
/// If the call is ambiguous (contains potentially non-nil arguments to both
|
|
/// the result and the error parameters), the \p ReturnErrorArgsIfAmbiguous
|
|
/// determines whether the success or error parameters are passed.
|
|
HandlerResult extractResultArgs(const CallExpr *CE,
|
|
bool ReturnErrorArgsIfAmbiguous) const;
|
|
|
|
// Convert the type of a success parameter in the completion handler function
|
|
// to a return type suitable for an async function. If there is an error
|
|
// parameter present e.g (T?, Error?) -> Void, this unwraps a level of
|
|
// optionality from T?. If this is a Result<T, U> type, returns the success
|
|
// type T.
|
|
swift::Type getSuccessParamAsyncReturnType(swift::Type Ty) const;
|
|
|
|
/// If the async function returns a tuple, the label of the \p Index -th
|
|
/// element in the returned tuple. If the function doesn't return a tuple or
|
|
/// the element is unlabeled, an empty identifier is returned.
|
|
Identifier getAsyncReturnTypeLabel(size_t Index) const;
|
|
|
|
/// Gets the return value types for the async equivalent of this handler.
|
|
ArrayRef<LabeledReturnType>
|
|
getAsyncReturnTypes(SmallVectorImpl<LabeledReturnType> &Scratch) const;
|
|
|
|
/// Whether the async equivalent of this handler returns Void.
|
|
bool willAsyncReturnVoid() const;
|
|
|
|
// TODO: If we have an async alternative we should check its result types
|
|
// for whether to unwrap or not
|
|
bool shouldUnwrap(swift::Type Ty) const {
|
|
return HasError && Ty->isOptional();
|
|
}
|
|
};
|
|
|
|
/// Given a completion handler that is part of a function signature, stores
|
|
/// information about that completion handler and its index within the function
|
|
/// declaration.
|
|
struct AsyncHandlerParamDesc : public AsyncHandlerDesc {
|
|
/// Enum to represent the position of the completion handler param within
|
|
/// the parameter list. Given `(A, B, C, D)`:
|
|
/// - A is `First`
|
|
/// - B and C are `Middle`
|
|
/// - D is `Last`
|
|
/// The position is `Only` if there's a single parameter that is the
|
|
/// completion handler and `None` if there is no handler.
|
|
enum class Position { First, Middle, Last, Only, None };
|
|
|
|
/// The function the completion handler is a parameter of.
|
|
const FuncDecl *Func = nullptr;
|
|
/// The index of the completion handler in the function that declares it.
|
|
unsigned Index = 0;
|
|
/// The async alternative, if one is found.
|
|
const AbstractFunctionDecl *Alternative = nullptr;
|
|
|
|
AsyncHandlerParamDesc() : AsyncHandlerDesc() {}
|
|
AsyncHandlerParamDesc(const AsyncHandlerDesc &Handler, const FuncDecl *Func,
|
|
unsigned Index, const AbstractFunctionDecl *Alternative)
|
|
: AsyncHandlerDesc(Handler), Func(Func), Index(Index),
|
|
Alternative(Alternative) {}
|
|
|
|
static AsyncHandlerParamDesc find(const FuncDecl *FD,
|
|
bool RequireAttributeOrName) {
|
|
if (!FD || FD->hasAsync() || FD->hasThrows() ||
|
|
!FD->getResultInterfaceType()->isVoid())
|
|
return AsyncHandlerParamDesc();
|
|
|
|
const auto *Alternative = FD->getAsyncAlternative();
|
|
std::optional<unsigned> Index =
|
|
FD->findPotentialCompletionHandlerParam(Alternative);
|
|
if (!Index)
|
|
return AsyncHandlerParamDesc();
|
|
|
|
bool RequireName = RequireAttributeOrName && !Alternative;
|
|
return AsyncHandlerParamDesc(
|
|
AsyncHandlerDesc::get(FD->getParameters()->get(*Index), RequireName),
|
|
FD, *Index, Alternative);
|
|
}
|
|
|
|
/// Build an @available attribute with the name of the async alternative as
|
|
/// the \c renamed argument, followed by a newline.
|
|
SmallString<128> buildRenamedAttribute() const {
|
|
SmallString<128> AvailabilityAttr;
|
|
llvm::raw_svector_ostream OS(AvailabilityAttr);
|
|
|
|
// If there's an alternative then there must already be an attribute,
|
|
// don't add another.
|
|
if (!isValid() || Alternative)
|
|
return AvailabilityAttr;
|
|
|
|
DeclName Name = Func->getName();
|
|
OS << "@available(*, renamed: \"" << Name.getBaseName() << "(";
|
|
ArrayRef<Identifier> ArgNames = Name.getArgumentNames();
|
|
for (size_t I = 0; I < ArgNames.size(); ++I) {
|
|
if (I != Index) {
|
|
OS << ArgNames[I] << tok::colon;
|
|
}
|
|
}
|
|
OS << ")\")\n";
|
|
|
|
return AvailabilityAttr;
|
|
}
|
|
|
|
/// Retrieves the parameter decl for the completion handler parameter, or
|
|
/// \c nullptr if no valid completion parameter is present.
|
|
const ParamDecl *getHandlerParam() const {
|
|
if (!isValid())
|
|
return nullptr;
|
|
return cast<ParamDecl>(getHandler());
|
|
}
|
|
|
|
/// See \c Position
|
|
Position handlerParamPosition() const {
|
|
if (!isValid())
|
|
return Position::None;
|
|
const auto *Params = Func->getParameters();
|
|
if (Params->size() == 1)
|
|
return Position::Only;
|
|
if (Index == 0)
|
|
return Position::First;
|
|
if (Index == Params->size() - 1)
|
|
return Position::Last;
|
|
return Position::Middle;
|
|
}
|
|
|
|
bool operator==(const AsyncHandlerParamDesc &Other) const {
|
|
return Handler == Other.Handler && Type == Other.Type &&
|
|
HasError == Other.HasError && Index == Other.Index;
|
|
}
|
|
|
|
bool alternativeIsAccessor() const {
|
|
return isa_and_nonnull<AccessorDecl>(Alternative);
|
|
}
|
|
};
|
|
|
|
/// The type of a condition in a conditional statement.
|
|
enum class ConditionType {
|
|
NIL, // == nil
|
|
NOT_NIL, // != nil
|
|
IS_TRUE, // if b
|
|
IS_FALSE, // if !b
|
|
SUCCESS_PATTERN, // case .success
|
|
FAILURE_PATTEN // case .failure
|
|
};
|
|
|
|
/// Indicates whether a condition describes a success or failure path. For
|
|
/// example, a check for whether an error parameter is present is a failure
|
|
/// path. A check for a nil error parameter is a success path. This is distinct
|
|
/// from ConditionType, as it relies on contextual information about what values
|
|
/// need to be checked for success or failure.
|
|
enum class ConditionPath { SUCCESS, FAILURE };
|
|
|
|
/// Finds the `Subject` being compared to in various conditions. Also finds any
|
|
/// pattern that may have a bound name.
|
|
struct CallbackCondition {
|
|
std::optional<ConditionType> Type;
|
|
const Decl *Subject = nullptr;
|
|
const Pattern *BindPattern = nullptr;
|
|
|
|
/// Initializes a `CallbackCondition` with a `!=` or `==` comparison of
|
|
/// an `Optional` typed `Subject` to `nil`, or a `Bool` typed `Subject` to a
|
|
/// boolean literal, ie.
|
|
/// - `<Subject> != nil`
|
|
/// - `<Subject> == nil`
|
|
/// - `<Subject> != true`
|
|
/// - `<Subject> == false`
|
|
CallbackCondition(const BinaryExpr *BE, const FuncDecl *Operator);
|
|
|
|
/// A bool condition expression.
|
|
explicit CallbackCondition(const Expr *E);
|
|
|
|
/// Initializes a `CallbackCondition` with binding of an `Optional` or
|
|
/// `Result` typed `Subject`, ie.
|
|
/// - `let bind = <Subject>`
|
|
/// - `case .success(let bind) = <Subject>`
|
|
/// - `case .failure(let bind) = <Subject>`
|
|
/// - `let bind = try? <Subject>.get()`
|
|
CallbackCondition(const Pattern *P, const Expr *Init);
|
|
|
|
/// Initializes a `CallbackCondtion` from a case statement inside a switch
|
|
/// on `Subject` with `Result` type, ie.
|
|
/// ```
|
|
/// switch <Subject> {
|
|
/// case .success(let bind):
|
|
/// case .failure(let bind):
|
|
/// }
|
|
/// ```
|
|
CallbackCondition(const Decl *Subject, const CaseLabelItem *CaseItem);
|
|
|
|
bool isValid() const { return Type.has_value(); }
|
|
|
|
private:
|
|
void initFromEnumPattern(const Decl *D, const EnumElementPattern *EEP);
|
|
|
|
void initFromOptionalTry(const class Pattern *P, const OptionalTryExpr *OTE);
|
|
};
|
|
|
|
/// A CallbackCondition with additional semantic information about whether it
|
|
/// is for a success path or failure path.
|
|
struct ClassifiedCondition : public CallbackCondition {
|
|
ConditionPath Path;
|
|
|
|
/// Whether this represents an Obj-C style boolean flag check for success.
|
|
bool IsObjCStyleFlagCheck;
|
|
|
|
explicit ClassifiedCondition(CallbackCondition Cond, ConditionPath Path,
|
|
bool IsObjCStyleFlagCheck)
|
|
: CallbackCondition(Cond), Path(Path),
|
|
IsObjCStyleFlagCheck(IsObjCStyleFlagCheck) {}
|
|
};
|
|
|
|
/// A wrapper for a map of parameter decls to their classified conditions, or
|
|
/// \c None if they are not present in any conditions.
|
|
struct ClassifiedCallbackConditions final
|
|
: llvm::MapVector<const Decl *, ClassifiedCondition> {
|
|
std::optional<ClassifiedCondition> lookup(const Decl *D) const {
|
|
auto Res = find(D);
|
|
if (Res == end())
|
|
return std::nullopt;
|
|
return Res->second;
|
|
}
|
|
};
|
|
|
|
/// A list of nodes to print, along with a list of locations that may have
|
|
/// preceding comments attached, which also need printing. For example:
|
|
///
|
|
/// \code
|
|
/// if .random() {
|
|
/// // a
|
|
/// print("hello")
|
|
/// // b
|
|
/// }
|
|
/// \endcode
|
|
///
|
|
/// To print out the contents of the if statement body, we'll include the AST
|
|
/// node for the \c print call. This will also include the preceding comment
|
|
/// \c a, but won't include the comment \c b. To ensure the comment \c b gets
|
|
/// printed, the SourceLoc for the closing brace \c } is added as a possible
|
|
/// comment loc.
|
|
class NodesToPrint {
|
|
SmallVector<ASTNode, 0> Nodes;
|
|
SmallVector<SourceLoc, 2> PossibleCommentLocs;
|
|
|
|
public:
|
|
NodesToPrint() {}
|
|
NodesToPrint(ArrayRef<ASTNode> Nodes, ArrayRef<SourceLoc> PossibleCommentLocs)
|
|
: Nodes(Nodes.begin(), Nodes.end()),
|
|
PossibleCommentLocs(PossibleCommentLocs.begin(),
|
|
PossibleCommentLocs.end()) {}
|
|
|
|
ArrayRef<ASTNode> getNodes() const { return Nodes; }
|
|
ArrayRef<SourceLoc> getPossibleCommentLocs() const {
|
|
return PossibleCommentLocs;
|
|
}
|
|
|
|
/// Add an AST node to print.
|
|
void addNode(ASTNode Node) {
|
|
// Note we skip vars as they'll be printed as a part of their
|
|
// PatternBindingDecl.
|
|
if (!Node.isDecl(DeclKind::Var))
|
|
Nodes.push_back(Node);
|
|
}
|
|
|
|
/// Add a SourceLoc which may have a preceding comment attached. If so, the
|
|
/// comment will be printed out at the appropriate location.
|
|
void addPossibleCommentLoc(SourceLoc Loc) {
|
|
if (Loc.isValid())
|
|
PossibleCommentLocs.push_back(Loc);
|
|
}
|
|
|
|
/// Add all the nodes in the brace statement to the list of nodes to print.
|
|
/// This should be preferred over adding the nodes manually as it picks up the
|
|
/// end location of the brace statement as a possible comment loc, ensuring
|
|
/// that we print any trailing comments in the brace statement.
|
|
void addNodesInBraceStmt(BraceStmt *Brace) {
|
|
for (auto Node : Brace->getElements())
|
|
addNode(Node);
|
|
|
|
// Ignore the end locations of implicit braces, as they're likely bogus.
|
|
// e.g for a case statement, the r-brace loc points to the last token of the
|
|
// last node in the body.
|
|
if (!Brace->isImplicit())
|
|
addPossibleCommentLoc(Brace->getRBraceLoc());
|
|
}
|
|
|
|
/// Add the nodes and comment locs from another NodesToPrint.
|
|
void addNodes(NodesToPrint OtherNodes) {
|
|
Nodes.append(OtherNodes.Nodes.begin(), OtherNodes.Nodes.end());
|
|
PossibleCommentLocs.append(OtherNodes.PossibleCommentLocs.begin(),
|
|
OtherNodes.PossibleCommentLocs.end());
|
|
}
|
|
|
|
/// Whether the last recorded node is an explicit return or break statement.
|
|
bool hasTrailingReturnOrBreak() const {
|
|
if (Nodes.empty())
|
|
return false;
|
|
return (Nodes.back().isStmt(StmtKind::Return) ||
|
|
Nodes.back().isStmt(StmtKind::Break)) &&
|
|
!Nodes.back().isImplicit();
|
|
}
|
|
|
|
/// If the last recorded node is an explicit return or break statement that
|
|
/// can be safely dropped, drop it from the list.
|
|
void dropTrailingReturnOrBreakIfPossible() {
|
|
if (!hasTrailingReturnOrBreak())
|
|
return;
|
|
|
|
auto *Node = cast<Stmt *>(Nodes.back());
|
|
|
|
// If this is a return statement with return expression, let's preserve it.
|
|
if (auto *RS = dyn_cast<ReturnStmt>(Node)) {
|
|
if (RS->hasResult())
|
|
return;
|
|
}
|
|
|
|
// Remove the node from the list, but make sure to add it as a possible
|
|
// comment loc to preserve any of its attached comments.
|
|
Nodes.pop_back();
|
|
addPossibleCommentLoc(Node->getStartLoc());
|
|
}
|
|
|
|
/// Returns a list of nodes to print in a brace statement. This picks up the
|
|
/// end location of the brace statement as a possible comment loc, ensuring
|
|
/// that we print any trailing comments in the brace statement.
|
|
static NodesToPrint inBraceStmt(BraceStmt *stmt) {
|
|
NodesToPrint Nodes;
|
|
Nodes.addNodesInBraceStmt(stmt);
|
|
return Nodes;
|
|
}
|
|
};
|
|
|
|
/// The statements within the closure of call to a function taking a callback
|
|
/// are split into a `SuccessBlock` and `ErrorBlock` (`ClassifiedBlocks`).
|
|
/// This class stores the nodes for each block, as well as a mapping of
|
|
/// decls to any patterns they are used in.
|
|
class ClassifiedBlock {
|
|
NodesToPrint Nodes;
|
|
|
|
// 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; }
|
|
|
|
/// 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();
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
const ParamPatternBindingsMap ¶mPatternBindings() const {
|
|
return ParamPatternBindings;
|
|
}
|
|
|
|
void addNodesInBraceStmt(BraceStmt *Brace) {
|
|
Nodes.addNodesInBraceStmt(Brace);
|
|
}
|
|
void addPossibleCommentLoc(SourceLoc Loc) {
|
|
Nodes.addPossibleCommentLoc(Loc);
|
|
}
|
|
void addAllNodes(NodesToPrint OtherNodes) {
|
|
Nodes.addNodes(std::move(OtherNodes));
|
|
}
|
|
|
|
void addNode(ASTNode Node) { Nodes.addNode(Node); }
|
|
|
|
void addBinding(const ClassifiedCondition &FromCondition) {
|
|
auto *P = FromCondition.BindPattern;
|
|
if (!P)
|
|
return;
|
|
|
|
// Patterns that don't bind anything aren't interesting.
|
|
SmallVector<VarDecl *, 2> Vars;
|
|
P->collectVariables(Vars);
|
|
if (Vars.empty())
|
|
return;
|
|
|
|
ParamPatternBindings[FromCondition.Subject].push_back(P);
|
|
}
|
|
|
|
void addAllBindings(const ClassifiedCallbackConditions &FromConditions) {
|
|
for (auto &Entry : FromConditions)
|
|
addBinding(Entry.second);
|
|
}
|
|
};
|
|
|
|
/// The type of block rewritten code may be placed in.
|
|
enum class BlockKind { SUCCESS, ERROR, FALLBACK };
|
|
|
|
/// A completion handler function parameter that is known to be a Bool flag
|
|
/// indicating success or failure.
|
|
struct KnownBoolFlagParam {
|
|
const ParamDecl *Param;
|
|
bool IsSuccessFlag;
|
|
};
|
|
|
|
/// A set of parameters for a completion callback closure.
|
|
class ClosureCallbackParams final {
|
|
const AsyncHandlerParamDesc &HandlerDesc;
|
|
ArrayRef<const ParamDecl *> AllParams;
|
|
llvm::SetVector<const ParamDecl *> SuccessParams;
|
|
const ParamDecl *ErrParam = nullptr;
|
|
std::optional<KnownBoolFlagParam> BoolFlagParam;
|
|
|
|
public:
|
|
ClosureCallbackParams(const AsyncHandlerParamDesc &HandlerDesc,
|
|
const ClosureExpr *Closure)
|
|
: HandlerDesc(HandlerDesc),
|
|
AllParams(Closure->getParameters()->getArray()) {
|
|
assert(AllParams.size() == HandlerDesc.params().size());
|
|
assert(HandlerDesc.Type != HandlerType::RESULT || AllParams.size() == 1);
|
|
|
|
SuccessParams.insert(AllParams.begin(), AllParams.end());
|
|
if (HandlerDesc.HasError && HandlerDesc.Type == HandlerType::PARAMS)
|
|
ErrParam = SuccessParams.pop_back_val();
|
|
|
|
// Check to see if we have a known bool flag parameter.
|
|
if (auto *AsyncAlt = HandlerDesc.Func->getAsyncAlternative()) {
|
|
if (auto Conv = AsyncAlt->getForeignAsyncConvention()) {
|
|
auto FlagIdx = Conv->completionHandlerFlagParamIndex();
|
|
if (FlagIdx && *FlagIdx >= 0 && *FlagIdx < AllParams.size()) {
|
|
auto IsSuccessFlag = Conv->completionHandlerFlagIsErrorOnZero();
|
|
BoolFlagParam = {AllParams[*FlagIdx], IsSuccessFlag};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whether the closure has a particular parameter.
|
|
bool hasParam(const ParamDecl *Param) const {
|
|
return Param == ErrParam || SuccessParams.contains(Param);
|
|
}
|
|
|
|
/// Whether \p Param is a success param.
|
|
bool isSuccessParam(const ParamDecl *Param) const {
|
|
return SuccessParams.contains(Param);
|
|
}
|
|
|
|
/// Whether \p Param is a closure parameter that may be unwrapped. This
|
|
/// includes optional parameters as well as \c Result parameters that may be
|
|
/// unwrapped through e.g 'try? res.get()'.
|
|
bool isUnwrappableParam(const ParamDecl *Param) const {
|
|
if (!hasParam(Param))
|
|
return false;
|
|
if (getResultParam() == Param)
|
|
return true;
|
|
return HandlerDesc.shouldUnwrap(Param->getTypeInContext());
|
|
}
|
|
|
|
/// Whether \p Param is the known Bool parameter that indicates success or
|
|
/// failure.
|
|
bool isKnownBoolFlagParam(const ParamDecl *Param) const {
|
|
if (auto BoolFlag = getKnownBoolFlagParam())
|
|
return BoolFlag->Param == Param;
|
|
return false;
|
|
}
|
|
|
|
/// Whether \p Param is a closure parameter that has a binding available in
|
|
/// the async variant of the call for a particular \p Block.
|
|
bool hasBinding(const ParamDecl *Param, BlockKind Block) const {
|
|
switch (Block) {
|
|
case BlockKind::SUCCESS:
|
|
// Known bool flags get dropped from the imported async variant.
|
|
if (isKnownBoolFlagParam(Param))
|
|
return false;
|
|
|
|
return isSuccessParam(Param);
|
|
case BlockKind::ERROR:
|
|
return Param == ErrParam;
|
|
case BlockKind::FALLBACK:
|
|
// We generally want to bind everything in the fallback case.
|
|
return hasParam(Param);
|
|
}
|
|
llvm_unreachable("Unhandled case in switch");
|
|
}
|
|
|
|
/// Retrieve the parameters to bind in a given \p Block.
|
|
TinyPtrVector<const ParamDecl *> getParamsToBind(BlockKind Block) {
|
|
TinyPtrVector<const ParamDecl *> Result;
|
|
for (auto *Param : AllParams) {
|
|
if (hasBinding(Param, Block))
|
|
Result.push_back(Param);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/// If there is a known Bool flag parameter indicating success or failure,
|
|
/// returns it, \c None otherwise.
|
|
std::optional<KnownBoolFlagParam> getKnownBoolFlagParam() const {
|
|
return BoolFlagParam;
|
|
}
|
|
|
|
/// All the parameters of the closure passed as the completion handler.
|
|
ArrayRef<const ParamDecl *> getAllParams() const { return AllParams; }
|
|
|
|
/// The success parameters of the closure passed as the completion handler.
|
|
/// Note this includes a \c Result parameter.
|
|
ArrayRef<const ParamDecl *> getSuccessParams() const {
|
|
return SuccessParams.getArrayRef();
|
|
}
|
|
|
|
/// The error parameter of the closure passed as the completion handler, or
|
|
/// \c nullptr if there is no error parameter.
|
|
const ParamDecl *getErrParam() const { return ErrParam; }
|
|
|
|
/// If the closure has a single \c Result parameter, returns it, \c nullptr
|
|
/// otherwise.
|
|
const ParamDecl *getResultParam() const {
|
|
return HandlerDesc.Type == HandlerType::RESULT ? SuccessParams[0] : nullptr;
|
|
}
|
|
};
|
|
|
|
struct ClassifiedBlocks {
|
|
ClassifiedBlock SuccessBlock;
|
|
ClassifiedBlock ErrorBlock;
|
|
};
|
|
|
|
/// Classifer of callback closure statements that that have either multiple
|
|
/// non-Result parameters or a single Result parameter and return Void.
|
|
///
|
|
/// It performs a (possibly incorrect) best effort and may give up in certain
|
|
/// cases. Aims to cover the idiomatic cases of either having no error
|
|
/// parameter at all, or having success/error code wrapped in ifs/guards/switch
|
|
/// using either pattern binding or nil checks.
|
|
///
|
|
/// Code outside any clear conditions is assumed to be solely part of the
|
|
/// success block for now, though some heuristics could be added to classify
|
|
/// these better in the future.
|
|
struct CallbackClassifier {
|
|
/// Updates the success and error block of `Blocks` with nodes and bound
|
|
/// names from `Body`. Errors are added through `DiagEngine`, possibly
|
|
/// resulting in partially filled out blocks.
|
|
static void classifyInto(ClassifiedBlocks &Blocks,
|
|
const ClosureCallbackParams &Params,
|
|
llvm::DenseSet<SwitchStmt *> &HandledSwitches,
|
|
DiagnosticEngine &DiagEngine, BraceStmt *Body);
|
|
|
|
private:
|
|
ClassifiedBlocks &Blocks;
|
|
const ClosureCallbackParams &Params;
|
|
llvm::DenseSet<SwitchStmt *> &HandledSwitches;
|
|
DiagnosticEngine &DiagEngine;
|
|
ClassifiedBlock *CurrentBlock;
|
|
|
|
/// This is set to \c true if we're currently classifying on a known condition
|
|
/// path, where \c CurrentBlock is set to the appropriate block. This lets us
|
|
/// be more lenient with unhandled conditions as we already know the block
|
|
/// we're supposed to be in.
|
|
bool IsKnownConditionPath = false;
|
|
|
|
CallbackClassifier(ClassifiedBlocks &Blocks,
|
|
const ClosureCallbackParams &Params,
|
|
llvm::DenseSet<SwitchStmt *> &HandledSwitches,
|
|
DiagnosticEngine &DiagEngine)
|
|
: Blocks(Blocks), Params(Params), HandledSwitches(HandledSwitches),
|
|
DiagEngine(DiagEngine), CurrentBlock(&Blocks.SuccessBlock) {}
|
|
|
|
/// Attempt to apply custom classification logic to a given node, returning
|
|
/// \c true if the node was classified, otherwise \c false.
|
|
bool tryClassifyNode(ASTNode Node);
|
|
|
|
/// Classify a node, or add the node to the block if it cannot be classified.
|
|
/// Returns \c true if there was an error.
|
|
bool classifyNode(ASTNode Node);
|
|
|
|
void classifyNodes(ArrayRef<ASTNode> Nodes, SourceLoc EndCommentLoc);
|
|
|
|
/// Whether any of the provided ASTNodes have a child expression that force
|
|
/// unwraps the error parameter. Note that this doesn't walk into new scopes.
|
|
bool hasForceUnwrappedErrorParam(ArrayRef<ASTNode> Nodes);
|
|
|
|
/// Given a callback condition, classify it as a success or failure path.
|
|
std::optional<ClassifiedCondition>
|
|
classifyCallbackCondition(const CallbackCondition &Cond,
|
|
const NodesToPrint &SuccessNodes, Stmt *ElseStmt);
|
|
|
|
/// Classifies all the conditions present in a given StmtCondition, taking
|
|
/// into account its success body and failure body. Returns \c true if there
|
|
/// were any conditions that couldn't be classified, \c false otherwise.
|
|
bool classifyConditionsOf(StmtCondition Cond,
|
|
const NodesToPrint &ThenNodesToPrint,
|
|
Stmt *ElseStmt,
|
|
ClassifiedCallbackConditions &Conditions);
|
|
|
|
/// Classifies the conditions of a conditional statement, and adds the
|
|
/// necessary nodes to either the success or failure block.
|
|
void classifyConditional(Stmt *Statement, StmtCondition Condition,
|
|
NodesToPrint ThenNodesToPrint, Stmt *ElseStmt);
|
|
|
|
/// Adds \p Nodes to \p Block, potentially flipping the current block if we
|
|
/// can determine that the nodes being added will cause control flow to leave
|
|
/// the scope.
|
|
///
|
|
/// \param Block The block to add the nodes to.
|
|
/// \param OtherBlock The block for the opposing condition path.
|
|
/// \param Nodes The nodes to add.
|
|
/// \param AlwaysExitsScope Whether the nodes being added always exit the
|
|
/// scope, and therefore whether the current block should be flipped.
|
|
void setNodes(ClassifiedBlock *Block, ClassifiedBlock *OtherBlock,
|
|
NodesToPrint Nodes, bool AlwaysExitsScope = false);
|
|
|
|
void classifySwitch(SwitchStmt *SS);
|
|
};
|
|
|
|
class DeclCollector : private SourceEntityWalker {
|
|
llvm::DenseSet<const Decl *> &Decls;
|
|
|
|
public:
|
|
/// Collect all explicit declarations declared in \p Scope (or \p SF if
|
|
/// \p Scope is a nullptr) that are not within their own scope.
|
|
static void collect(BraceStmt *Scope, SourceFile &SF,
|
|
llvm::DenseSet<const Decl *> &Decls);
|
|
|
|
private:
|
|
DeclCollector(llvm::DenseSet<const Decl *> &Decls) : Decls(Decls) {}
|
|
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) override;
|
|
|
|
bool walkToExprPre(Expr *E) override;
|
|
|
|
bool walkToStmtPre(Stmt *S) override;
|
|
};
|
|
|
|
class ReferenceCollector : private SourceEntityWalker {
|
|
SourceManager *SM;
|
|
llvm::DenseSet<const Decl *> DeclaredDecls;
|
|
llvm::DenseSet<const Decl *> &ReferencedDecls;
|
|
|
|
ASTNode Target;
|
|
bool AfterTarget;
|
|
|
|
public:
|
|
/// Collect all explicit references in \p Scope (or \p SF if \p Scope is
|
|
/// a nullptr) that are after \p Target and not first declared. That is,
|
|
/// references that we don't want to shadow with hoisted declarations.
|
|
///
|
|
/// Also collect all declarations that are \c DeclContexts, which is an
|
|
/// over-appoximation but let's us ignore them elsewhere.
|
|
static void collect(ASTNode Target, BraceStmt *Scope, SourceFile &SF,
|
|
llvm::DenseSet<const Decl *> &Decls);
|
|
|
|
private:
|
|
ReferenceCollector(ASTNode Target, SourceManager *SM,
|
|
llvm::DenseSet<const Decl *> &Decls)
|
|
: SM(SM), DeclaredDecls(), ReferencedDecls(Decls), Target(Target),
|
|
AfterTarget(false) {}
|
|
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) override;
|
|
|
|
bool walkToExprPre(Expr *E) override;
|
|
|
|
bool walkToStmtPre(Stmt *S) override;
|
|
|
|
bool walkToPatternPre(Pattern *P) override;
|
|
|
|
bool shouldWalkInto(SourceRange Range);
|
|
};
|
|
|
|
/// Similar to the \c ReferenceCollector but collects references in all scopes
|
|
/// without any starting point in each scope. In addition, it tracks the number
|
|
/// of references to a decl in a given scope.
|
|
class ScopedDeclCollector : private SourceEntityWalker {
|
|
public:
|
|
using DeclsTy = llvm::DenseSet<const Decl *>;
|
|
using RefDeclsTy = llvm::DenseMap<const Decl *, /*numRefs*/ unsigned>;
|
|
|
|
private:
|
|
using ScopedDeclsTy = llvm::DenseMap<const Stmt *, RefDeclsTy>;
|
|
|
|
struct Scope {
|
|
DeclsTy DeclaredDecls;
|
|
RefDeclsTy *ReferencedDecls;
|
|
Scope(RefDeclsTy *ReferencedDecls)
|
|
: DeclaredDecls(), ReferencedDecls(ReferencedDecls) {}
|
|
};
|
|
|
|
ScopedDeclsTy ReferencedDecls;
|
|
llvm::SmallVector<Scope, 4> ScopeStack;
|
|
|
|
public:
|
|
/// Starting at \c Scope, collect all explicit references in every scope
|
|
/// within (including the initial) that are not first declared, ie. those that
|
|
/// could end up shadowed. Also include all \c DeclContext declarations as
|
|
/// we'd like to avoid renaming functions and types completely.
|
|
void collect(ASTNode Node) { walk(Node); }
|
|
|
|
const RefDeclsTy *getReferencedDecls(Stmt *Scope) const;
|
|
|
|
private:
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) override;
|
|
|
|
bool walkToExprPre(Expr *E) override;
|
|
|
|
bool walkToStmtPre(Stmt *S) override;
|
|
|
|
bool walkToStmtPost(Stmt *S) override;
|
|
};
|
|
|
|
/// 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;
|
|
|
|
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);
|
|
};
|
|
|
|
/// Builds up async-converted code for an AST node.
|
|
///
|
|
/// If it is a function, its declaration will have `async` added. If a
|
|
/// completion handler is present, it will be removed and the return type of
|
|
/// the function will reflect the parameters of the handler, including an
|
|
/// added `throws` if necessary.
|
|
///
|
|
/// Calls to the completion handler are replaced with either a `return` or
|
|
/// `throws` depending on the arguments.
|
|
///
|
|
/// Calls to functions with an async alternative will be replaced with a call
|
|
/// to the alternative, possibly wrapped in a do/catch. The do/catch is skipped
|
|
/// if the closure either:
|
|
/// 1. Has no error
|
|
/// 2. Has an error but no error handling (eg. just ignores)
|
|
/// 3. Has error handling that only calls the containing function's handler
|
|
/// with an error matching the error argument
|
|
///
|
|
/// (2) is technically not the correct translation, but in practice it's likely
|
|
/// the code a user would actually want.
|
|
///
|
|
/// If the success vs error handling split inside the closure cannot be
|
|
/// determined and the closure takes regular parameters (ie. not a Result), a
|
|
/// fallback translation is used that keeps all the same variable names and
|
|
/// simply moves the code within the closure out.
|
|
///
|
|
/// The fallback is generally avoided, however, since it's quite unlikely to be
|
|
/// 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;
|
|
|
|
// Node to convert
|
|
ASTNode StartNode;
|
|
|
|
// Completion handler of `StartNode` (if it's a function with an async
|
|
// alternative)
|
|
AsyncHandlerParamDesc TopHandler;
|
|
|
|
SmallString<0> Buffer;
|
|
llvm::raw_svector_ostream OS;
|
|
|
|
// Any initializer expressions in a shorthand if that we need to skip (as it
|
|
// points to the same identifier as the declaration itself).
|
|
llvm::DenseSet<const Expr *> shorthandIfInits;
|
|
|
|
// Decls where any force unwrap or optional chain of that decl should be
|
|
// elided, e.g for a previously optional closure parameter that has become a
|
|
// non-optional local.
|
|
llvm::DenseSet<const Decl *> Unwraps;
|
|
|
|
// Decls whose references should be replaced with, either because they no
|
|
// longer exist or are a different type. Any replaced code should ideally be
|
|
// handled by the refactoring properly, but that's not possible in all cases
|
|
llvm::DenseSet<const Decl *> Placeholders;
|
|
|
|
// Mapping from decl -> name, used as the name of possible new local
|
|
// declarations of old completion handler parametes, as well as the
|
|
// replacement for other hoisted declarations and their references
|
|
llvm::DenseMap<const Decl *, Identifier> Names;
|
|
|
|
/// 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.
|
|
ScopedDeclCollector ScopedDecls;
|
|
|
|
/// The switch statements that have been re-written by this transform.
|
|
llvm::DenseSet<SwitchStmt *> HandledSwitches;
|
|
|
|
// The last source location that has been output. Used to output the source
|
|
// between handled nodes
|
|
SourceLoc LastAddedLoc;
|
|
|
|
// Number of expressions (or pattern binding decl) currently nested in, taking
|
|
// into account hoisting and the possible removal of ifs/switches
|
|
int NestedExprCount = 0;
|
|
|
|
// Whether a completion handler body is currently being hoisted out of its
|
|
// 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,
|
|
DiagnosticEngine &DiagEngine, AbstractFunctionDecl *FD,
|
|
const AsyncHandlerParamDesc &TopHandler)
|
|
: SF(SF), SM(SM), DiagEngine(DiagEngine), StartNode(FD),
|
|
TopHandler(TopHandler), OS(Buffer) {
|
|
Placeholders.insert(TopHandler.getHandler());
|
|
ScopedDecls.collect(FD);
|
|
|
|
// Shouldn't strictly be necessary, but prefer possible shadowing over
|
|
// crashes caused by a missing scope
|
|
addNewScope({});
|
|
}
|
|
|
|
/// Convert a call
|
|
AsyncConverter(SourceFile *SF, SourceManager &SM,
|
|
DiagnosticEngine &DiagEngine, CallExpr *CE, BraceStmt *Scope)
|
|
: SF(SF), SM(SM), DiagEngine(DiagEngine), StartNode(CE), OS(Buffer) {
|
|
ScopedDecls.collect(CE);
|
|
|
|
// Create the initial scope, can be more accurate than the general
|
|
// \c ScopedDeclCollector as there is a starting point.
|
|
llvm::DenseSet<const Decl *> UsedDecls;
|
|
DeclCollector::collect(Scope, *SF, UsedDecls);
|
|
ReferenceCollector::collect(StartNode, Scope, *SF, UsedDecls);
|
|
addNewScope(UsedDecls);
|
|
}
|
|
|
|
ASTContext &getASTContext() const { return SF->getASTContext(); }
|
|
|
|
bool convert();
|
|
|
|
/// When adding an async alternative method for the function declaration \c
|
|
/// FD, this function tries to create a function body for the legacy function
|
|
/// (the one with a completion handler), which calls the newly converted async
|
|
/// function. There are certain situations in which we fail to create such a
|
|
/// body, e.g. if the completion handler has the signature `(String, Error?)
|
|
/// -> Void` in which case we can't synthesize the result of type \c String in
|
|
/// the error case.
|
|
bool createLegacyBody();
|
|
|
|
/// Creates an async alternative function that forwards onto the completion
|
|
/// handler function through
|
|
/// withCheckedContinuation/withCheckedThrowingContinuation.
|
|
bool createAsyncWrapper();
|
|
|
|
void replace(ASTNode Node, SourceEditConsumer &EditConsumer,
|
|
SourceLoc StartOverride = SourceLoc());
|
|
|
|
void insertAfter(ASTNode Node, SourceEditConsumer &EditConsumer);
|
|
|
|
private:
|
|
bool canCreateLegacyBody();
|
|
|
|
/// Prints a tuple of elements, or a lone single element if only one is
|
|
/// present, using the provided printing function.
|
|
template <typename Container, typename PrintFn>
|
|
void addTupleOf(const Container &Elements, llvm::raw_ostream &OS,
|
|
PrintFn PrintElt) {
|
|
if (Elements.size() == 1) {
|
|
PrintElt(Elements[0]);
|
|
return;
|
|
}
|
|
OS << tok::l_paren;
|
|
llvm::interleave(Elements, PrintElt, [&]() { OS << tok::comma << " "; });
|
|
OS << tok::r_paren;
|
|
}
|
|
|
|
/// Retrieve the completion handler closure argument for an async wrapper
|
|
/// function.
|
|
std::string
|
|
getAsyncWrapperCompletionClosure(StringRef ContName,
|
|
const AsyncHandlerParamDesc &HandlerDesc);
|
|
|
|
/// Retrieves the SourceRange of the preceding comment, or an invalid range if
|
|
/// there is no preceding comment.
|
|
CharSourceRange getPrecedingCommentRange(SourceLoc Loc);
|
|
|
|
/// Retrieves the location for the start of a comment attached to the token
|
|
/// at the provided location, or the location itself if there is no comment.
|
|
SourceLoc getLocIncludingPrecedingComment(SourceLoc Loc);
|
|
|
|
/// If the provided SourceLoc has a preceding comment, print it out.
|
|
void printCommentIfNeeded(SourceLoc Loc);
|
|
|
|
void convertNodes(const NodesToPrint &ToPrint);
|
|
|
|
void convertNode(ASTNode Node, SourceLoc StartOverride = {},
|
|
bool ConvertCalls = true,
|
|
bool IncludePrecedingComment = true);
|
|
|
|
void convertPattern(const Pattern *P);
|
|
|
|
/// 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);
|
|
|
|
bool walkToPatternPre(Pattern *P) override;
|
|
|
|
bool walkToDeclPre(Decl *D, CharSourceRange Range) override;
|
|
|
|
bool walkToDeclPost(Decl *D) override;
|
|
|
|
bool walkToExprPre(Expr *E) override;
|
|
|
|
bool replaceRangeWithPlaceholder(SourceRange range);
|
|
|
|
bool walkToExprPost(Expr *E) override;
|
|
|
|
bool walkToStmtPre(Stmt *S) override;
|
|
|
|
bool walkToStmtPost(Stmt *S) override;
|
|
|
|
bool addCustom(SourceRange Range, llvm::function_ref<void()> Custom = {});
|
|
|
|
/// 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 = {});
|
|
|
|
void addRange(SourceLoc Start, SourceLoc End, bool ToEndOfToken = false);
|
|
|
|
void addRange(SourceRange Range, bool ToEndOfToken = false);
|
|
|
|
void addFuncDecl(const FuncDecl *FD);
|
|
|
|
void addFallbackVars(ArrayRef<const ParamDecl *> FallbackParams,
|
|
const ClosureCallbackParams &AllParams);
|
|
|
|
void addDo();
|
|
|
|
/// Assuming that \p Result represents an error result to completion handler,
|
|
/// returns \c true if the error has already been handled through a
|
|
/// 'try await'.
|
|
bool isErrorAlreadyHandled(HandlerResult Result);
|
|
|
|
/// Returns \c true if the source representation of \p E can be interpreted
|
|
/// as an expression returning an Optional value.
|
|
bool isExpressionOptional(Expr *E);
|
|
|
|
/// Converts a call \p CE to a completion handler. Depending on the call it
|
|
/// will be interpreted as a call that's returning a success result, an error
|
|
/// or, if the call is completely ambiguous, adds an if-let that checks if the
|
|
/// error is \c nil at runtime and dispatches to the success or error case
|
|
/// depending on it.
|
|
/// \p AddConvertedHandlerCall needs to add the converted version of the
|
|
/// completion handler. Depending on the given \c HandlerResult, it must be
|
|
/// intepreted as a success or error call.
|
|
/// \p AddConvertedErrorCall must add the converted equivalent of returning an
|
|
/// error. The passed \c StringRef contains the name of a variable that is of
|
|
/// type 'Error'.
|
|
void convertHandlerCall(
|
|
const CallExpr *CE,
|
|
llvm::function_ref<void(HandlerResult)> AddConvertedHandlerCall,
|
|
llvm::function_ref<void(StringRef)> AddConvertedErrorCall);
|
|
|
|
/// Convert a call \p CE to a completion handler to its 'return' or 'throws'
|
|
/// equivalent.
|
|
void convertHandlerToReturnOrThrows(const CallExpr *CE);
|
|
|
|
/// Convert the call \p CE to a completion handler to its 'return' or 'throws'
|
|
/// equivalent, where \p Result determines whether the call should be
|
|
/// interpreted as an error or success call.
|
|
void convertHandlerToReturnOrThrowsImpl(const CallExpr *CE,
|
|
HandlerResult Result);
|
|
|
|
/// Convert a call \p CE to a completion handler to resumes of the
|
|
/// continuation that's currently on top of the stack.
|
|
void convertHandlerToContinuationResume(const CallExpr *CE);
|
|
|
|
/// Convert a call \p CE to a completion handler to resumes of the
|
|
/// continuation that's currently on top of the stack.
|
|
/// \p Result determines whether the call should be interpreted as a success
|
|
/// or error call.
|
|
void convertHandlerToContinuationResumeImpl(const CallExpr *CE,
|
|
HandlerResult Result);
|
|
|
|
/// 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);
|
|
|
|
/// Callback arguments marked as e.g. `@convention(block)` produce arguments
|
|
/// that are `FunctionConversionExpr`.
|
|
/// We don't care about the conversions and want to shave them off.
|
|
Expr *lookThroughFunctionConversionExpr(Expr *E);
|
|
|
|
void addHoistedCallback(const CallExpr *CE,
|
|
const AsyncHandlerParamDesc &HandlerDesc);
|
|
|
|
/// Add a binding to a known bool flag that indicates success or failure.
|
|
void addBoolFlagParamBindingIfNeeded(std::optional<KnownBoolFlagParam> Flag,
|
|
BlockKind Block);
|
|
|
|
/// Add a call to the async alternative of \p CE and convert the \p Callback
|
|
/// to be executed after the async call. \p HandlerDesc describes the
|
|
/// completion handler in the function that's called by \p CE and \p ArgList
|
|
/// are the arguments being passed in \p CE.
|
|
void addHoistedClosureCallback(const CallExpr *CE,
|
|
const AsyncHandlerParamDesc &HandlerDesc,
|
|
const ClosureExpr *Callback);
|
|
|
|
/// Add a call to the async alternative of \p FD. Afterwards, pass the results
|
|
/// of the async call to the completion handler, named \p HandlerName and
|
|
/// described by \p HandlerDesc.
|
|
/// \p AddAwaitCall adds the call to the refactored async method to the output
|
|
/// stream without storing the result to any variables.
|
|
/// This is used when the user didn't use a closure for the callback, but
|
|
/// passed in a variable or function name for the completion handler.
|
|
void addHoistedNamedCallback(const FuncDecl *FD,
|
|
const AsyncHandlerDesc &HandlerDesc,
|
|
StringRef HandlerName,
|
|
std::function<void(void)> AddAwaitCall);
|
|
|
|
/// 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);
|
|
|
|
/// 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);
|
|
|
|
/// 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);
|
|
|
|
/// Prints an \c await call to an \c async function, binding any return values
|
|
/// into variables.
|
|
///
|
|
/// \param CE The call expr to convert.
|
|
/// \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, const ClassifiedBlock &SuccessBlock,
|
|
ArrayRef<const ParamDecl *> SuccessParams,
|
|
const InlinePatternsToPrint &InlinePatterns,
|
|
const AsyncHandlerParamDesc &HandlerDesc,
|
|
bool AddDeclarations);
|
|
|
|
void addFallbackCatch(const ClosureCallbackParams &Params);
|
|
|
|
void addCatch(const ParamDecl *ErrParam);
|
|
|
|
void preparePlaceholdersAndUnwraps(AsyncHandlerDesc HandlerDesc,
|
|
const ClosureCallbackParams &Params,
|
|
BlockKind Block);
|
|
|
|
/// Add a mapping from each passed parameter to a new name, possibly
|
|
/// synthesizing a new one if hoisting it would cause a redeclaration or
|
|
/// shadowing. If there's no bound name and \c AddIfMissing is false, no
|
|
/// name will be added.
|
|
void prepareNames(const ClassifiedBlock &Block,
|
|
ArrayRef<const ParamDecl *> Params,
|
|
const InlinePatternsToPrint &InlinePatterns,
|
|
bool AddIfMissing = true);
|
|
|
|
/// Returns a unique name using \c Name as base that doesn't clash with any
|
|
/// other names in the current scope.
|
|
Identifier createUniqueName(StringRef Name);
|
|
|
|
/// 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 Scopes.Names).
|
|
Identifier assignUniqueName(const Decl *D, StringRef BoundName);
|
|
|
|
StringRef newNameFor(const Decl *D, bool Required = true);
|
|
|
|
void addNewScope(const llvm::DenseSet<const Decl *> &Decls);
|
|
|
|
void clearNames(ArrayRef<const ParamDecl *> Params);
|
|
|
|
/// Adds a forwarding call to the old completion handler function, with
|
|
/// \p HandlerReplacement that allows for a custom replacement or, if empty,
|
|
/// removal of the completion handler closure.
|
|
void addForwardingCallTo(const FuncDecl *FD, StringRef HandlerReplacement);
|
|
|
|
/// Adds a forwarded error argument to a completion handler call. If the error
|
|
/// type of \p HandlerDesc is more specialized than \c Error, an
|
|
/// 'as! CustomError' cast to the more specialized error type will be added to
|
|
/// the output stream.
|
|
void addForwardedErrorArgument(StringRef ErrorName,
|
|
const AsyncHandlerDesc &HandlerDesc);
|
|
|
|
/// If \p T has a natural default value like \c nil for \c Optional or \c ()
|
|
/// for \c Void, add that default value to the output. Otherwise, add a
|
|
/// placeholder that contains \p T's name as the hint.
|
|
void addDefaultValueOrPlaceholder(Type T);
|
|
|
|
/// Adds the \c Index -th parameter to the completion handler described by \p
|
|
/// HanderDesc.
|
|
/// If \p ResultName is not empty, it is assumed that a variable with that
|
|
/// name contains the result returned from the async alternative. If the
|
|
/// callback also takes an error parameter, \c nil passed to the completion
|
|
/// handler for the error. If \p ResultName is empty, it is a assumed that a
|
|
/// variable named 'error' contains the error thrown from the async method and
|
|
/// 'nil' will be passed to the completion handler for all result parameters.
|
|
void addCompletionHandlerArgument(size_t Index, StringRef ResultName,
|
|
const AsyncHandlerDesc &HandlerDesc);
|
|
|
|
/// Add a call to the completion handler named \p HandlerName and described by
|
|
/// \p HandlerDesc, passing all the required arguments. See \c
|
|
/// getCompletionHandlerArgument for how the arguments are synthesized.
|
|
void addCallToCompletionHandler(StringRef ResultName,
|
|
const AsyncHandlerDesc &HandlerDesc,
|
|
StringRef HandlerName);
|
|
|
|
/// Adds the result type of a refactored async function that previously
|
|
/// returned results via a completion handler described by \p HandlerDesc.
|
|
void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc);
|
|
|
|
/// If \p FD is generic, adds a type annotation with the return type of the
|
|
/// converted async function. This is used when creating a legacy function,
|
|
/// calling the converted 'async' function so that the generic parameters of
|
|
/// the legacy function are passed to the generic function. For example for
|
|
/// \code
|
|
/// func foo<GenericParam>() async -> GenericParam {}
|
|
/// \endcode
|
|
/// we generate
|
|
/// \code
|
|
/// func foo<GenericParam>(completion: (GenericParam) -> Void) {
|
|
/// Task {
|
|
/// let result: GenericParam = await foo()
|
|
/// <------------>
|
|
/// completion(result)
|
|
/// }
|
|
/// }
|
|
/// \endcode
|
|
/// This function adds the range marked by \c <----->
|
|
void addResultTypeAnnotationIfNecessary(const FuncDecl *FD,
|
|
const AsyncHandlerDesc &HandlerDesc);
|
|
};
|
|
|
|
} // namespace asyncrefactorings
|
|
} // namespace refactoring
|
|
} // namespace swift
|
|
|
|
#endif
|