Changeset View
Changeset View
Standalone View
Standalone View
clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
Show First 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | |||||
#include "llvm/Support/raw_os_ostream.h" | #include "llvm/Support/raw_os_ostream.h" | ||||
namespace clang { | namespace clang { | ||||
namespace clangd { | namespace clangd { | ||||
namespace { | namespace { | ||||
using Node = SelectionTree::Node; | using Node = SelectionTree::Node; | ||||
struct HoistSetComparator { | |||||
bool operator()(const Decl *const Lhs, const Decl *const Rhs) const { | |||||
return Lhs->getLocation() < Rhs->getLocation(); | |||||
} | |||||
}; | |||||
using HoistSet = llvm::SmallSet<const NamedDecl *, 1, HoistSetComparator>; | |||||
// ExtractionZone is the part of code that is being extracted. | // ExtractionZone is the part of code that is being extracted. | ||||
// EnclosingFunction is the function/method inside which the zone lies. | // EnclosingFunction is the function/method inside which the zone lies. | ||||
// We split the file into 4 parts relative to extraction zone. | // We split the file into 4 parts relative to extraction zone. | ||||
enum class ZoneRelative { | enum class ZoneRelative { | ||||
Before, // Before Zone and inside EnclosingFunction. | Before, // Before Zone and inside EnclosingFunction. | ||||
Inside, // Inside Zone. | Inside, // Inside Zone. | ||||
After, // After Zone and inside EnclosingFunction. | After, // After Zone and inside EnclosingFunction. | ||||
OutsideFunc // Outside EnclosingFunction. | OutsideFunc // Outside EnclosingFunction. | ||||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | struct ExtractionZone { | ||||
SourceLocation getInsertionPoint() const { | SourceLocation getInsertionPoint() const { | ||||
return EnclosingFuncRange.getBegin(); | return EnclosingFuncRange.getBegin(); | ||||
} | } | ||||
bool isRootStmt(const Stmt *S) const; | bool isRootStmt(const Stmt *S) const; | ||||
// The last root statement is important to decide where we need to insert a | // The last root statement is important to decide where we need to insert a | ||||
// semicolon after the extraction. | // semicolon after the extraction. | ||||
const Node *getLastRootStmt() const { return Parent->Children.back(); } | const Node *getLastRootStmt() const { return Parent->Children.back(); } | ||||
// Checks if declarations inside extraction zone are accessed afterwards. | // Checks if declarations inside extraction zone are accessed afterwards and | ||||
// adds these declarations to the returned set. | |||||
// | // | ||||
// This performs a partial AST traversal proportional to the size of the | // This performs a partial AST traversal proportional to the size of the | ||||
// enclosing function, so it is possibly expensive. | // enclosing function, so it is possibly expensive. | ||||
bool requiresHoisting(const SourceManager &SM, | HoistSet getDeclsToHoist(const SourceManager &SM, | ||||
const HeuristicResolver *Resolver) const { | const HeuristicResolver *Resolver) const { | ||||
// First find all the declarations that happened inside extraction zone. | // First find all the declarations that happened inside extraction zone. | ||||
llvm::SmallSet<const Decl *, 1> DeclsInExtZone; | llvm::SmallSet<const Decl *, 1> DeclsInExtZone; | ||||
for (auto *RootStmt : RootStmts) { | for (auto *RootStmt : RootStmts) { | ||||
findExplicitReferences( | findExplicitReferences( | ||||
RootStmt, | RootStmt, | ||||
[&DeclsInExtZone](const ReferenceLoc &Loc) { | [&DeclsInExtZone](const ReferenceLoc &Loc) { | ||||
if (!Loc.IsDecl) | if (!Loc.IsDecl) | ||||
return; | return; | ||||
DeclsInExtZone.insert(Loc.Targets.front()); | DeclsInExtZone.insert(Loc.Targets.front()); | ||||
}, | }, | ||||
Resolver); | Resolver); | ||||
} | } | ||||
// Early exit without performing expensive traversal below. | // Early exit without performing expensive traversal below. | ||||
if (DeclsInExtZone.empty()) | if (DeclsInExtZone.empty()) | ||||
return false; | return {}; | ||||
// Then make sure they are not used outside the zone. | // Add any decl used after the selection to the returned set | ||||
HoistSet DeclsToHoist{}; | |||||
for (const auto *S : EnclosingFunction->getBody()->children()) { | for (const auto *S : EnclosingFunction->getBody()->children()) { | ||||
if (SM.isBeforeInTranslationUnit(S->getSourceRange().getEnd(), | if (SM.isBeforeInTranslationUnit(S->getSourceRange().getEnd(), | ||||
ZoneRange.getEnd())) | ZoneRange.getEnd())) | ||||
continue; | continue; | ||||
bool HasPostUse = false; | |||||
findExplicitReferences( | findExplicitReferences( | ||||
S, | S, | ||||
[&](const ReferenceLoc &Loc) { | [&](const ReferenceLoc &Loc) { | ||||
if (HasPostUse || | if (SM.isBeforeInTranslationUnit(Loc.NameLoc, ZoneRange.getEnd())) | ||||
SM.isBeforeInTranslationUnit(Loc.NameLoc, ZoneRange.getEnd())) | |||||
return; | return; | ||||
HasPostUse = llvm::any_of(Loc.Targets, | const auto *const PostUseIter = llvm::find_if( | ||||
[&DeclsInExtZone](const Decl *Target) { | Loc.Targets, [&DeclsInExtZone](const Decl *Target) { | ||||
return DeclsInExtZone.contains(Target); | return DeclsInExtZone.contains(Target); | ||||
}); | }); | ||||
if (const bool FoundPostUse = PostUseIter != Loc.Targets.end(); | |||||
FoundPostUse) { | |||||
DeclsToHoist.insert(*PostUseIter); | |||||
} | |||||
}, | }, | ||||
Resolver); | Resolver); | ||||
if (HasPostUse) | |||||
return true; | |||||
} | } | ||||
return false; | return DeclsToHoist; | ||||
} | } | ||||
}; | }; | ||||
// Whether the code in the extraction zone is guaranteed to return, assuming | // Whether the code in the extraction zone is guaranteed to return, assuming | ||||
// no broken control flow (unbound break/continue). | // no broken control flow (unbound break/continue). | ||||
// This is a very naive check (does it end with a return stmt). | // This is a very naive check (does it end with a return stmt). | ||||
// Doing some rudimentary control flow analysis would cover more cases. | // Doing some rudimentary control flow analysis would cover more cases. | ||||
bool alwaysReturns(const ExtractionZone &EZ) { | bool alwaysReturns(const ExtractionZone &EZ) { | ||||
▲ Show 20 Lines • Show All 137 Lines • ▼ Show 20 Lines | struct NewFunction { | ||||
const NestedNameSpecifier *DefinitionQualifier = nullptr; | const NestedNameSpecifier *DefinitionQualifier = nullptr; | ||||
const DeclContext *SemanticDC = nullptr; | const DeclContext *SemanticDC = nullptr; | ||||
const DeclContext *SyntacticDC = nullptr; | const DeclContext *SyntacticDC = nullptr; | ||||
const DeclContext *ForwardDeclarationSyntacticDC = nullptr; | const DeclContext *ForwardDeclarationSyntacticDC = nullptr; | ||||
bool CallerReturnsValue = false; | bool CallerReturnsValue = false; | ||||
bool Static = false; | bool Static = false; | ||||
ConstexprSpecKind Constexpr = ConstexprSpecKind::Unspecified; | ConstexprSpecKind Constexpr = ConstexprSpecKind::Unspecified; | ||||
bool Const = false; | bool Const = false; | ||||
const HoistSet &ToHoist; | |||||
// Decides whether the extracted function body and the function call need a | // Decides whether the extracted function body and the function call need a | ||||
// semicolon after extraction. | // semicolon after extraction. | ||||
tooling::ExtractionSemicolonPolicy SemicolonPolicy; | tooling::ExtractionSemicolonPolicy SemicolonPolicy; | ||||
const LangOptions *LangOpts; | const LangOptions *LangOpts; | ||||
NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy, | NewFunction(const HoistSet &ToHoist, | ||||
tooling::ExtractionSemicolonPolicy SemicolonPolicy, | |||||
const LangOptions *LangOpts) | const LangOptions *LangOpts) | ||||
: SemicolonPolicy(SemicolonPolicy), LangOpts(LangOpts) {} | : ToHoist(ToHoist), SemicolonPolicy(SemicolonPolicy), LangOpts(LangOpts) { | ||||
} | |||||
// Render the call for this function. | // Render the call for this function. | ||||
std::string renderCall() const; | std::string renderCall() const; | ||||
std::string renderHoistedCall() const; | |||||
// Render the definition for this function. | // Render the definition for this function. | ||||
std::string renderDeclaration(FunctionDeclKind K, | std::string renderDeclaration(FunctionDeclKind K, | ||||
const DeclContext &SemanticDC, | const DeclContext &SemanticDC, | ||||
const DeclContext &SyntacticDC, | const DeclContext &SyntacticDC, | ||||
const SourceManager &SM) const; | const SourceManager &SM) const; | ||||
private: | private: | ||||
std::string | std::string | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | std::string NewFunction::renderDeclarationName(FunctionDeclKind K) const { | ||||
} | } | ||||
std::string QualifierName; | std::string QualifierName; | ||||
llvm::raw_string_ostream Oss(QualifierName); | llvm::raw_string_ostream Oss(QualifierName); | ||||
DefinitionQualifier->print(Oss, *LangOpts); | DefinitionQualifier->print(Oss, *LangOpts); | ||||
return llvm::formatv("{0}{1}", QualifierName, Name); | return llvm::formatv("{0}{1}", QualifierName, Name); | ||||
} | } | ||||
// Renders the HoistSet to a comma separated list or a single named decl. | |||||
std::string renderHoistSet(const HoistSet &ToHoist) { | |||||
std::string Res{}; | |||||
bool NeedsComma = false; | |||||
const auto Render = [&NeedsComma, &Res](const NamedDecl *const NDecl) { | |||||
if (NeedsComma) { | |||||
Res += ", "; | |||||
} | |||||
Res += NDecl->getNameAsString(); | |||||
}; | |||||
for (const NamedDecl *DeclToHoist : ToHoist) { | |||||
if (llvm::isa<VarDecl>(DeclToHoist) || | |||||
llvm::isa<BindingDecl>(DeclToHoist)) { | |||||
Render(DeclToHoist); | |||||
} | |||||
NeedsComma = true; | |||||
} | |||||
return Res; | |||||
} | |||||
std::string NewFunction::renderHoistedCall() const { | |||||
auto HoistedVarDecls = std::string{}; | |||||
auto ExplicitUnpacking = std::string{}; | |||||
const auto HasStructuredBinding = LangOpts->CPlusPlus17; | |||||
if (ToHoist.size() > 1) { | |||||
if (HasStructuredBinding) { | |||||
HoistedVarDecls = "auto [" + renderHoistSet(ToHoist) + "] = "; | |||||
} else { | |||||
HoistedVarDecls = "auto returned = "; | |||||
auto DeclIter = ToHoist.begin(); | |||||
for (size_t Index = 0U; Index < ToHoist.size(); ++Index, ++DeclIter) { | |||||
ExplicitUnpacking += | |||||
llvm::formatv("\nauto {0} = std::get<{1}>(returned);", | |||||
(*DeclIter)->getNameAsString(), Index); | |||||
} | |||||
} | |||||
} else { | |||||
HoistedVarDecls = "auto " + renderHoistSet(ToHoist) + " = "; | |||||
} | |||||
return std::string(llvm::formatv( | |||||
"{0}{1}({2}){3}{4}", HoistedVarDecls, Name, renderParametersForCall(), | |||||
(SemicolonPolicy.isNeededInOriginalFunction() ? ";" : ""), | |||||
ExplicitUnpacking)); | |||||
} | |||||
std::string NewFunction::renderCall() const { | std::string NewFunction::renderCall() const { | ||||
if (!ToHoist.empty()) | |||||
return renderHoistedCall(); | |||||
return std::string( | return std::string( | ||||
llvm::formatv("{0}{1}({2}){3}", CallerReturnsValue ? "return " : "", Name, | llvm::formatv("{0}{1}({2}){3}", CallerReturnsValue ? "return " : "", Name, | ||||
renderParametersForCall(), | renderParametersForCall(), | ||||
(SemicolonPolicy.isNeededInOriginalFunction() ? ";" : ""))); | (SemicolonPolicy.isNeededInOriginalFunction() ? ";" : ""))); | ||||
} | } | ||||
std::string NewFunction::renderDeclaration(FunctionDeclKind K, | std::string NewFunction::renderDeclaration(FunctionDeclKind K, | ||||
const DeclContext &SemanticDC, | const DeclContext &SemanticDC, | ||||
Show All 16 Lines | std::string NewFunction::renderDeclaration(FunctionDeclKind K, | ||||
llvm_unreachable("Unsupported FunctionDeclKind enum"); | llvm_unreachable("Unsupported FunctionDeclKind enum"); | ||||
} | } | ||||
std::string NewFunction::getFuncBody(const SourceManager &SM) const { | std::string NewFunction::getFuncBody(const SourceManager &SM) const { | ||||
// FIXME: Generate tooling::Replacements instead of std::string to | // FIXME: Generate tooling::Replacements instead of std::string to | ||||
// - hoist decls | // - hoist decls | ||||
// - add return statement | // - add return statement | ||||
// - Add semicolon | // - Add semicolon | ||||
return toSourceCode(SM, BodyRange).str() + | auto Body = toSourceCode(SM, BodyRange).str() + | ||||
(SemicolonPolicy.isNeededInExtractedFunction() ? ";" : ""); | (SemicolonPolicy.isNeededInExtractedFunction() ? ";" : ""); | ||||
if (!ToHoist.empty()) { | |||||
if (const bool NeedsTupleOrPair = ToHoist.size() > 1; NeedsTupleOrPair) { | |||||
const auto NeedsPair = ToHoist.size() == 2; | |||||
Body += "\nreturn " + | |||||
std::string(NeedsPair ? "std::pair{" : "std::tuple{") + | |||||
renderHoistSet(ToHoist) + "};"; | |||||
} else { | |||||
Body += "\nreturn " + renderHoistSet(ToHoist) + ";"; | |||||
} | |||||
} | |||||
return Body; | |||||
} | } | ||||
std::string NewFunction::Parameter::render(const DeclContext *Context) const { | std::string NewFunction::Parameter::render(const DeclContext *Context) const { | ||||
return printType(TypeInfo, *Context) + (PassByReference ? " &" : " ") + Name; | return printType(TypeInfo, *Context) + (PassByReference ? " &" : " ") + Name; | ||||
} | } | ||||
// Stores captured information about Extraction Zone. | // Stores captured information about Extraction Zone. | ||||
struct CapturedZoneInfo { | struct CapturedZoneInfo { | ||||
▲ Show 20 Lines • Show All 161 Lines • ▼ Show 20 Lines | |||||
// needed. | // needed. | ||||
// FIXME: Check if the declaration has a local/anonymous type | // FIXME: Check if the declaration has a local/anonymous type | ||||
bool createParameters(NewFunction &ExtractedFunc, | bool createParameters(NewFunction &ExtractedFunc, | ||||
const CapturedZoneInfo &CapturedInfo) { | const CapturedZoneInfo &CapturedInfo) { | ||||
for (const auto &KeyVal : CapturedInfo.DeclInfoMap) { | for (const auto &KeyVal : CapturedInfo.DeclInfoMap) { | ||||
const auto &DeclInfo = KeyVal.second; | const auto &DeclInfo = KeyVal.second; | ||||
// If a Decl was Declared in zone and referenced in post zone, it | // If a Decl was Declared in zone and referenced in post zone, it | ||||
// needs to be hoisted (we bail out in that case). | // needs to be hoisted (we bail out in that case). | ||||
// FIXME: Support Decl Hoisting. | |||||
if (DeclInfo.DeclaredIn == ZoneRelative::Inside && | |||||
DeclInfo.IsReferencedInPostZone) | |||||
return false; | |||||
if (!DeclInfo.IsReferencedInZone) | if (!DeclInfo.IsReferencedInZone) | ||||
continue; // no need to pass as parameter, not referenced | continue; // no need to pass as parameter, not referenced | ||||
if (DeclInfo.DeclaredIn == ZoneRelative::Inside || | if (DeclInfo.DeclaredIn == ZoneRelative::Inside || | ||||
DeclInfo.DeclaredIn == ZoneRelative::OutsideFunc) | DeclInfo.DeclaredIn == ZoneRelative::OutsideFunc) | ||||
continue; // no need to pass as parameter, still accessible. | continue; // no need to pass as parameter, still accessible. | ||||
// Parameter specific checks. | // Parameter specific checks. | ||||
const ValueDecl *VD = dyn_cast_or_null<ValueDecl>(DeclInfo.TheDecl); | const ValueDecl *VD = dyn_cast_or_null<ValueDecl>(DeclInfo.TheDecl); | ||||
// Can't parameterise if the Decl isn't a ValueDecl or is a FunctionDecl | // Can't parameterise if the Decl isn't a ValueDecl or is a FunctionDecl | ||||
Show All 29 Lines | getSemicolonPolicy(ExtractionZone &ExtZone, const SourceManager &SM, | ||||
auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute( | auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute( | ||||
ExtZone.getLastRootStmt()->ASTNode.get<Stmt>(), FuncBodyRange, SM, | ExtZone.getLastRootStmt()->ASTNode.get<Stmt>(), FuncBodyRange, SM, | ||||
LangOpts); | LangOpts); | ||||
// Update ZoneRange. | // Update ZoneRange. | ||||
ExtZone.ZoneRange.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1)); | ExtZone.ZoneRange.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1)); | ||||
return SemicolonPolicy; | return SemicolonPolicy; | ||||
} | } | ||||
QualType getReturnTypeForHoisted(const FunctionDecl &EnclosingFunc, | |||||
const HoistSet &ToHoist) { | |||||
// Hoisting just one variable, use that variables type instead of auto | |||||
if (ToHoist.size() == 1) { | |||||
if (const auto *const VDecl = llvm::dyn_cast<VarDecl>(*ToHoist.begin()); | |||||
VDecl != nullptr) { | |||||
return VDecl->getType(); | |||||
} | |||||
} | |||||
return EnclosingFunc.getParentASTContext().getAutoDeductType(); | |||||
} | |||||
// Generate return type for ExtractedFunc. Return false if unable to do so. | // Generate return type for ExtractedFunc. Return false if unable to do so. | ||||
bool generateReturnProperties(NewFunction &ExtractedFunc, | bool generateReturnProperties(NewFunction &ExtractedFunc, | ||||
const FunctionDecl &EnclosingFunc, | const FunctionDecl &EnclosingFunc, | ||||
const CapturedZoneInfo &CapturedInfo) { | const CapturedZoneInfo &CapturedInfo) { | ||||
// If the selected code always returns, we preserve those return statements. | // If the selected code always returns, we preserve those return statements. | ||||
// The return type should be the same as the enclosing function. | // The return type should be the same as the enclosing function. | ||||
// (Others are possible if there are conversions, but this seems clearest). | // (Others are possible if there are conversions, but this seems clearest). | ||||
if (CapturedInfo.HasReturnStmt) { | if (CapturedInfo.HasReturnStmt) { | ||||
// If the return is conditional, neither replacing the code with | // If the return is conditional, neither replacing the code with | ||||
// `extracted()` nor `return extracted()` is correct. | // `extracted()` nor `return extracted()` is correct. | ||||
if (!CapturedInfo.AlwaysReturns) | if (!CapturedInfo.AlwaysReturns) | ||||
return false; | return false; | ||||
QualType Ret = EnclosingFunc.getReturnType(); | QualType Ret = EnclosingFunc.getReturnType(); | ||||
// Once we support members, it'd be nice to support e.g. extracting a method | // Once we support members, it'd be nice to support e.g. extracting a method | ||||
// of Foo<T> that returns T. But it's not clear when that's safe. | // of Foo<T> that returns T. But it's not clear when that's safe. | ||||
if (Ret->isDependentType()) | if (Ret->isDependentType()) | ||||
return false; | return false; | ||||
ExtractedFunc.ReturnType = Ret; | ExtractedFunc.ReturnType = Ret; | ||||
return true; | return true; | ||||
} | } | ||||
// FIXME: Generate new return statement if needed. | // FIXME: Generate new return statement if needed. | ||||
ExtractedFunc.ReturnType = EnclosingFunc.getParentASTContext().VoidTy; | ExtractedFunc.ReturnType = | ||||
!ExtractedFunc.ToHoist.empty() | |||||
? getReturnTypeForHoisted(EnclosingFunc, ExtractedFunc.ToHoist) | |||||
: EnclosingFunc.getParentASTContext().VoidTy; | |||||
return true; | return true; | ||||
} | } | ||||
void captureMethodInfo(NewFunction &ExtractedFunc, | void captureMethodInfo(NewFunction &ExtractedFunc, | ||||
const CXXMethodDecl *Method) { | const CXXMethodDecl *Method) { | ||||
ExtractedFunc.Static = Method->isStatic(); | ExtractedFunc.Static = Method->isStatic(); | ||||
ExtractedFunc.Const = Method->isConst(); | ExtractedFunc.Const = Method->isConst(); | ||||
ExtractedFunc.EnclosingClass = Method->getParent(); | ExtractedFunc.EnclosingClass = Method->getParent(); | ||||
} | } | ||||
// FIXME: add support for adding other function return types besides void. | // FIXME: add support for adding other function return types besides void. | ||||
// FIXME: assign the value returned by non void extracted function. | // FIXME: assign the value returned by non void extracted function. | ||||
llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone, | llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone, | ||||
const HoistSet &ToHoist, | |||||
const SourceManager &SM, | const SourceManager &SM, | ||||
const LangOptions &LangOpts) { | const LangOptions &LangOpts) { | ||||
CapturedZoneInfo CapturedInfo = captureZoneInfo(ExtZone); | CapturedZoneInfo CapturedInfo = captureZoneInfo(ExtZone); | ||||
// Bail out if any break of continue exists | // Bail out if any break of continue exists | ||||
if (CapturedInfo.BrokenControlFlow) | if (CapturedInfo.BrokenControlFlow) | ||||
return error("Cannot extract break/continue without corresponding " | return error("Cannot extract break/continue without corresponding " | ||||
"loop/switch statement."); | "loop/switch statement."); | ||||
NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts), | NewFunction ExtractedFunc(ToHoist, getSemicolonPolicy(ExtZone, SM, LangOpts), | ||||
&LangOpts); | &LangOpts); | ||||
ExtractedFunc.SyntacticDC = | ExtractedFunc.SyntacticDC = | ||||
ExtZone.EnclosingFunction->getLexicalDeclContext(); | ExtZone.EnclosingFunction->getLexicalDeclContext(); | ||||
ExtractedFunc.SemanticDC = ExtZone.EnclosingFunction->getDeclContext(); | ExtractedFunc.SemanticDC = ExtZone.EnclosingFunction->getDeclContext(); | ||||
ExtractedFunc.DefinitionQualifier = ExtZone.EnclosingFunction->getQualifier(); | ExtractedFunc.DefinitionQualifier = ExtZone.EnclosingFunction->getQualifier(); | ||||
ExtractedFunc.Constexpr = ExtZone.EnclosingFunction->getConstexprKind(); | ExtractedFunc.Constexpr = ExtZone.EnclosingFunction->getConstexprKind(); | ||||
Show All 32 Lines | public: | ||||
Expected<Effect> apply(const Selection &Inputs) override; | Expected<Effect> apply(const Selection &Inputs) override; | ||||
std::string title() const override { return "Extract to function"; } | std::string title() const override { return "Extract to function"; } | ||||
llvm::StringLiteral kind() const override { | llvm::StringLiteral kind() const override { | ||||
return CodeAction::REFACTOR_KIND; | return CodeAction::REFACTOR_KIND; | ||||
} | } | ||||
private: | private: | ||||
ExtractionZone ExtZone; | ExtractionZone ExtZone; | ||||
HoistSet ToHoist; | |||||
}; | }; | ||||
REGISTER_TWEAK(ExtractFunction) | REGISTER_TWEAK(ExtractFunction) | ||||
tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc, | tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc, | ||||
const SourceManager &SM, | const SourceManager &SM, | ||||
const LangOptions &LangOpts) { | const LangOptions &LangOpts) { | ||||
std::string FuncCall = ExtractedFunc.renderCall(); | std::string FuncCall = ExtractedFunc.renderCall(); | ||||
return tooling::Replacement( | return tooling::Replacement( | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | if (!LangOpts.CPlusPlus) | ||||
return false; | return false; | ||||
const Node *CommonAnc = Inputs.ASTSelection.commonAncestor(); | const Node *CommonAnc = Inputs.ASTSelection.commonAncestor(); | ||||
const SourceManager &SM = Inputs.AST->getSourceManager(); | const SourceManager &SM = Inputs.AST->getSourceManager(); | ||||
auto MaybeExtZone = findExtractionZone(CommonAnc, SM, LangOpts); | auto MaybeExtZone = findExtractionZone(CommonAnc, SM, LangOpts); | ||||
if (!MaybeExtZone || | if (!MaybeExtZone || | ||||
(hasReturnStmt(*MaybeExtZone) && !alwaysReturns(*MaybeExtZone))) | (hasReturnStmt(*MaybeExtZone) && !alwaysReturns(*MaybeExtZone))) | ||||
return false; | return false; | ||||
// FIXME: Get rid of this check once we support hoisting. | ToHoist = | ||||
if (MaybeExtZone->requiresHoisting(SM, Inputs.AST->getHeuristicResolver())) | MaybeExtZone->getDeclsToHoist(SM, Inputs.AST->getHeuristicResolver()); | ||||
const auto HasAutoReturnTypeDeduction = LangOpts.CPlusPlus14; | |||||
const auto RequiresPairOrTuple = ToHoist.size() > 1; | |||||
if (RequiresPairOrTuple && !HasAutoReturnTypeDeduction) | |||||
return false; | return false; | ||||
ExtZone = std::move(*MaybeExtZone); | ExtZone = std::move(*MaybeExtZone); | ||||
return true; | return true; | ||||
} | } | ||||
Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) { | Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) { | ||||
const SourceManager &SM = Inputs.AST->getSourceManager(); | const SourceManager &SM = Inputs.AST->getSourceManager(); | ||||
const LangOptions &LangOpts = Inputs.AST->getLangOpts(); | const LangOptions &LangOpts = Inputs.AST->getLangOpts(); | ||||
auto ExtractedFunc = getExtractedFunction(ExtZone, SM, LangOpts); | auto ExtractedFunc = getExtractedFunction(ExtZone, ToHoist, SM, LangOpts); | ||||
// FIXME: Add more types of errors. | // FIXME: Add more types of errors. | ||||
if (!ExtractedFunc) | if (!ExtractedFunc) | ||||
return ExtractedFunc.takeError(); | return ExtractedFunc.takeError(); | ||||
tooling::Replacements Edit; | tooling::Replacements Edit; | ||||
if (auto Err = Edit.add(createFunctionDefinition(*ExtractedFunc, SM))) | if (auto Err = Edit.add(createFunctionDefinition(*ExtractedFunc, SM))) | ||||
return std::move(Err); | return std::move(Err); | ||||
if (auto Err = Edit.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts))) | if (auto Err = Edit.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts))) | ||||
return std::move(Err); | return std::move(Err); | ||||
if (auto FwdLoc = ExtractedFunc->ForwardDeclarationPoint) { | if (auto FwdLoc = ExtractedFunc->ForwardDeclarationPoint) { | ||||
// If the fwd-declaration goes in the same file, merge into Replacements. | // If the fwd-declaration goes in the same file, merge into Replacements. | ||||
// Otherwise it needs to be a separate file edit. | // Otherwise it needs to be a separate file edit. | ||||
if (SM.isWrittenInSameFile(ExtractedFunc->DefinitionPoint, *FwdLoc)) { | if (SM.isWrittenInSameFile(ExtractedFunc->DefinitionPoint, *FwdLoc)) { | ||||
if (auto Err = Edit.add(createForwardDeclaration(*ExtractedFunc, SM))) | if (auto Err = Edit.add(createForwardDeclaration(*ExtractedFunc, SM))) | ||||
return std::move(Err); | return std::move(Err); | ||||
} else { | } else { | ||||
auto MultiFileEffect = Effect::mainFileEdit(SM, std::move(Edit)); | auto MultiFileEffect = Effect::mainFileEdit(SM, std::move(Edit)); | ||||
if (!MultiFileEffect) | if (!MultiFileEffect) | ||||
return MultiFileEffect.takeError(); | return MultiFileEffect.takeError(); | ||||
tooling::Replacements OtherEdit( | tooling::Replacements OtherEdit( | ||||
createForwardDeclaration(*ExtractedFunc, SM)); | createForwardDeclaration(*ExtractedFunc, SM)); | ||||
if (auto PathAndEdit = Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc), | if (auto PathAndEdit = | ||||
OtherEdit)) | Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc), OtherEdit)) | ||||
MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first, | MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first, | ||||
PathAndEdit->second); | PathAndEdit->second); | ||||
else | else | ||||
return PathAndEdit.takeError(); | return PathAndEdit.takeError(); | ||||
return MultiFileEffect; | return MultiFileEffect; | ||||
} | } | ||||
} | } | ||||
return Effect::mainFileEdit(SM, std::move(Edit)); | return Effect::mainFileEdit(SM, std::move(Edit)); | ||||
} | } | ||||
} // namespace | } // namespace | ||||
} // namespace clangd | } // namespace clangd | ||||
} // namespace clang | } // namespace clang |