forked from OSchip/llvm-project
[Clangd] Initial version of ExtractFunction
Summary: - Only works for extraction from free functions - Basic analysis of the code being extracted. - Extract to void function - Bail out if extracting a return, continue or break. - Doesn't hoist decls yet Reviewers: kadircet, sammccall Subscribers: mgorny, ilya-biryukov, MaskRay, jkorous, arphaman, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D65526 llvm-svn: 370249
This commit is contained in:
parent
2d4b6777c4
commit
bf4773485e
|
@ -16,6 +16,7 @@ add_clang_library(clangDaemonTweaks OBJECT
|
|||
DumpAST.cpp
|
||||
ExpandAutoType.cpp
|
||||
ExpandMacro.cpp
|
||||
ExtractFunction.cpp
|
||||
ExtractVariable.cpp
|
||||
RawStringLiteral.cpp
|
||||
SwapIfBranches.cpp
|
||||
|
|
|
@ -0,0 +1,605 @@
|
|||
//===--- ExtractFunction.cpp -------------------------------------*- C++-*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Extracts statements to a new function and replaces the statements with a
|
||||
// call to the new function.
|
||||
// Before:
|
||||
// void f(int a) {
|
||||
// [[if(a < 5)
|
||||
// a = 5;]]
|
||||
// }
|
||||
// After:
|
||||
// void extracted(int &a) {
|
||||
// if(a < 5)
|
||||
// a = 5;
|
||||
// }
|
||||
// void f(int a) {
|
||||
// extracted(a);
|
||||
// }
|
||||
//
|
||||
// - Only extract statements
|
||||
// - Extracts from non-templated free functions only.
|
||||
// - Parameters are const only if the declaration was const
|
||||
// - Always passed by l-value reference
|
||||
// - Void return type
|
||||
// - Cannot extract declarations that will be needed in the original function
|
||||
// after extraction.
|
||||
// - Doesn't check for broken control flow (break/continue without loop/switch)
|
||||
//
|
||||
// 1. ExtractFunction is the tweak subclass
|
||||
// - Prepare does basic analysis of the selection and is therefore fast.
|
||||
// Successful prepare doesn't always mean we can apply the tweak.
|
||||
// - Apply does a more detailed analysis and can be slower. In case of
|
||||
// failure, we let the user know that we are unable to perform extraction.
|
||||
// 2. ExtractionZone store information about the range being extracted and the
|
||||
// enclosing function.
|
||||
// 3. NewFunction stores properties of the extracted function and provides
|
||||
// methods for rendering it.
|
||||
// 4. CapturedZoneInfo uses a RecursiveASTVisitor to capture information about
|
||||
// the extraction like declarations, existing return statements, etc.
|
||||
// 5. getExtractedFunction is responsible for analyzing the CapturedZoneInfo and
|
||||
// creating a NewFunction.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "AST.h"
|
||||
#include "ClangdUnit.h"
|
||||
#include "Logger.h"
|
||||
#include "Selection.h"
|
||||
#include "SourceCode.h"
|
||||
#include "refactor/Tweak.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/DeclTemplate.h"
|
||||
#include "clang/AST/RecursiveASTVisitor.h"
|
||||
#include "clang/AST/Stmt.h"
|
||||
#include "clang/Basic/LangOptions.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "clang/Lex/Lexer.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "clang/Tooling/Refactoring/Extract/SourceExtraction.h"
|
||||
#include "llvm/ADT/None.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/iterator_range.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace {
|
||||
|
||||
using Node = SelectionTree::Node;
|
||||
|
||||
// ExtractionZone is the part of code that is being extracted.
|
||||
// EnclosingFunction is the function/method inside which the zone lies.
|
||||
// We split the file into 4 parts relative to extraction zone.
|
||||
enum class ZoneRelative {
|
||||
Before, // Before Zone and inside EnclosingFunction.
|
||||
Inside, // Inside Zone.
|
||||
After, // After Zone and inside EnclosingFunction.
|
||||
OutsideFunc // Outside EnclosingFunction.
|
||||
};
|
||||
|
||||
// A RootStmt is a statement that's fully selected including all it's children
|
||||
// and it's parent is unselected.
|
||||
// Check if a node is a root statement.
|
||||
bool isRootStmt(const Node *N) {
|
||||
if (!N->ASTNode.get<Stmt>())
|
||||
return false;
|
||||
// Root statement cannot be partially selected.
|
||||
if (N->Selected == SelectionTree::Partial)
|
||||
return false;
|
||||
// Only DeclStmt can be an unselected RootStmt since VarDecls claim the entire
|
||||
// selection range in selectionTree.
|
||||
if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the (unselected) parent of all RootStmts given the commonAncestor.
|
||||
// We only support extraction of RootStmts since it allows us to extract without
|
||||
// having to change the selection range. Also, this means that any scope that
|
||||
// begins in selection range, ends in selection range and any scope that begins
|
||||
// outside the selection range, ends outside as well.
|
||||
const Node *getParentOfRootStmts(const Node *CommonAnc) {
|
||||
if (!CommonAnc)
|
||||
return nullptr;
|
||||
switch (CommonAnc->Selected) {
|
||||
case SelectionTree::Selection::Unselected:
|
||||
// Ensure all Children are RootStmts.
|
||||
return llvm::all_of(CommonAnc->Children, isRootStmt) ? CommonAnc : nullptr;
|
||||
case SelectionTree::Selection::Partial:
|
||||
// Treat Partially selected VarDecl as completely selected since
|
||||
// SelectionTree doesn't always select VarDecls correctly.
|
||||
// FIXME: Remove this after D66872 is upstream)
|
||||
if (!CommonAnc->ASTNode.get<VarDecl>())
|
||||
return nullptr;
|
||||
LLVM_FALLTHROUGH;
|
||||
case SelectionTree::Selection::Complete:
|
||||
// If the Common Ancestor is completely selected, then it's a root statement
|
||||
// and its parent will be unselected.
|
||||
const Node *Parent = CommonAnc->Parent;
|
||||
// If parent is a DeclStmt, even though it's unselected, we consider it a
|
||||
// root statement and return its parent. This is done because the VarDecls
|
||||
// claim the entire selection range of the Declaration and DeclStmt is
|
||||
// always unselected.
|
||||
return Parent->ASTNode.get<DeclStmt>() ? Parent->Parent : Parent;
|
||||
}
|
||||
}
|
||||
|
||||
// The ExtractionZone class forms a view of the code wrt Zone.
|
||||
struct ExtractionZone {
|
||||
// Parent of RootStatements being extracted.
|
||||
const Node *Parent = nullptr;
|
||||
// The half-open file range of the code being extracted.
|
||||
SourceRange ZoneRange;
|
||||
// The function inside which our zone resides.
|
||||
const FunctionDecl *EnclosingFunction = nullptr;
|
||||
// The half-open file range of the enclosing function.
|
||||
SourceRange EnclosingFuncRange;
|
||||
SourceLocation getInsertionPoint() const {
|
||||
return EnclosingFuncRange.getBegin();
|
||||
}
|
||||
bool isRootStmt(const Stmt *S) const;
|
||||
// The last root statement is important to decide where we need to insert a
|
||||
// semicolon after the extraction.
|
||||
const Node *getLastRootStmt() const { return Parent->Children.back(); }
|
||||
void generateRootStmts();
|
||||
private:
|
||||
llvm::DenseSet<const Stmt *> RootStmts;
|
||||
};
|
||||
|
||||
bool ExtractionZone::isRootStmt(const Stmt *S) const {
|
||||
return RootStmts.find(S) != RootStmts.end();
|
||||
}
|
||||
|
||||
// Generate RootStmts set
|
||||
void ExtractionZone::generateRootStmts() {
|
||||
for(const Node *Child : Parent->Children)
|
||||
RootStmts.insert(Child->ASTNode.get<Stmt>());
|
||||
}
|
||||
|
||||
// Finds the function in which the zone lies.
|
||||
const FunctionDecl *findEnclosingFunction(const Node *CommonAnc) {
|
||||
// Walk up the SelectionTree until we find a function Decl
|
||||
for (const Node *CurNode = CommonAnc; CurNode; CurNode = CurNode->Parent) {
|
||||
// Don't extract from lambdas
|
||||
if (CurNode->ASTNode.get<LambdaExpr>())
|
||||
return nullptr;
|
||||
if (const FunctionDecl *Func = CurNode->ASTNode.get<FunctionDecl>()) {
|
||||
// FIXME: Support extraction from methods.
|
||||
if (isa<CXXMethodDecl>(Func))
|
||||
return nullptr;
|
||||
// FIXME: Support extraction from templated functions.
|
||||
if(Func->isTemplated())
|
||||
return nullptr;
|
||||
return Func;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Zone Range is the union of SourceRanges of all child Nodes in Parent since
|
||||
// all child Nodes are RootStmts
|
||||
llvm::Optional<SourceRange> findZoneRange(const Node *Parent,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
SourceRange SR;
|
||||
if (auto BeginFileRange = toHalfOpenFileRange(
|
||||
SM, LangOpts, Parent->Children.front()->ASTNode.getSourceRange()))
|
||||
SR.setBegin(BeginFileRange->getBegin());
|
||||
else
|
||||
return llvm::None;
|
||||
if (auto EndFileRange = toHalfOpenFileRange(
|
||||
SM, LangOpts, Parent->Children.back()->ASTNode.getSourceRange()))
|
||||
SR.setEnd(EndFileRange->getEnd());
|
||||
else
|
||||
return llvm::None;
|
||||
return SR;
|
||||
}
|
||||
|
||||
// Compute the range spanned by the enclosing function.
|
||||
// FIXME: check if EnclosingFunction has any attributes as the AST doesn't
|
||||
// always store the source range of the attributes and thus we end up extracting
|
||||
// between the attributes and the EnclosingFunction.
|
||||
llvm::Optional<SourceRange>
|
||||
computeEnclosingFuncRange(const FunctionDecl *EnclosingFunction,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
return toHalfOpenFileRange(SM, LangOpts, EnclosingFunction->getSourceRange());
|
||||
}
|
||||
|
||||
// FIXME: Check we're not extracting from the initializer/condition of a control
|
||||
// flow structure.
|
||||
// FIXME: Check that we don't extract the compound statement of the
|
||||
// enclosingFunction.
|
||||
llvm::Optional<ExtractionZone> findExtractionZone(const Node *CommonAnc,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
ExtractionZone ExtZone;
|
||||
ExtZone.Parent = getParentOfRootStmts(CommonAnc);
|
||||
if (!ExtZone.Parent || ExtZone.Parent->Children.empty())
|
||||
return llvm::None;
|
||||
// Don't extract expressions.
|
||||
// FIXME: We should extract expressions that are "statements" i.e. not
|
||||
// subexpressions
|
||||
if (ExtZone.Parent->Children.size() == 1 &&
|
||||
ExtZone.getLastRootStmt()->ASTNode.get<Expr>())
|
||||
return llvm::None;
|
||||
ExtZone.EnclosingFunction = findEnclosingFunction(ExtZone.Parent);
|
||||
if (!ExtZone.EnclosingFunction)
|
||||
return llvm::None;
|
||||
if (auto FuncRange =
|
||||
computeEnclosingFuncRange(ExtZone.EnclosingFunction, SM, LangOpts))
|
||||
ExtZone.EnclosingFuncRange = *FuncRange;
|
||||
if (auto ZoneRange = findZoneRange(ExtZone.Parent, SM, LangOpts))
|
||||
ExtZone.ZoneRange = *ZoneRange;
|
||||
if (ExtZone.EnclosingFuncRange.isInvalid() || ExtZone.ZoneRange.isInvalid())
|
||||
return llvm::None;
|
||||
ExtZone.generateRootStmts();
|
||||
return ExtZone;
|
||||
}
|
||||
|
||||
// Stores information about the extracted function and provides methods for
|
||||
// rendering it.
|
||||
struct NewFunction {
|
||||
struct Parameter {
|
||||
std::string Name;
|
||||
QualType TypeInfo;
|
||||
bool PassByReference;
|
||||
unsigned OrderPriority; // Lower value parameters are preferred first.
|
||||
std::string render(const DeclContext *Context) const;
|
||||
bool operator<(const Parameter &Other) const {
|
||||
return OrderPriority < Other.OrderPriority;
|
||||
}
|
||||
};
|
||||
std::string Name = "extracted";
|
||||
std::string ReturnType;
|
||||
std::vector<Parameter> Parameters;
|
||||
SourceRange BodyRange;
|
||||
SourceLocation InsertionPoint;
|
||||
const DeclContext *EnclosingFuncContext;
|
||||
// Decides whether the extracted function body and the function call need a
|
||||
// semicolon after extraction.
|
||||
tooling::ExtractionSemicolonPolicy SemicolonPolicy;
|
||||
NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy)
|
||||
: SemicolonPolicy(SemicolonPolicy) {}
|
||||
// Render the call for this function.
|
||||
std::string renderCall() const;
|
||||
// Render the definition for this function.
|
||||
std::string renderDefinition(const SourceManager &SM) const;
|
||||
|
||||
private:
|
||||
std::string renderParametersForDefinition() const;
|
||||
std::string renderParametersForCall() const;
|
||||
// Generate the function body.
|
||||
std::string getFuncBody(const SourceManager &SM) const;
|
||||
};
|
||||
|
||||
std::string NewFunction::renderParametersForDefinition() const {
|
||||
std::string Result;
|
||||
bool NeedCommaBefore = false;
|
||||
for (const Parameter &P : Parameters) {
|
||||
if (NeedCommaBefore)
|
||||
Result += ", ";
|
||||
NeedCommaBefore = true;
|
||||
Result += P.render(EnclosingFuncContext);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::string NewFunction::renderParametersForCall() const {
|
||||
std::string Result;
|
||||
bool NeedCommaBefore = false;
|
||||
for (const Parameter &P : Parameters) {
|
||||
if (NeedCommaBefore)
|
||||
Result += ", ";
|
||||
NeedCommaBefore = true;
|
||||
Result += P.Name;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::string NewFunction::renderCall() const {
|
||||
return Name + "(" + renderParametersForCall() + ")" +
|
||||
(SemicolonPolicy.isNeededInOriginalFunction() ? ";" : "");
|
||||
}
|
||||
|
||||
std::string NewFunction::renderDefinition(const SourceManager &SM) const {
|
||||
return ReturnType + " " + Name + "(" + renderParametersForDefinition() + ")" +
|
||||
" {\n" + getFuncBody(SM) + "\n}\n";
|
||||
}
|
||||
|
||||
std::string NewFunction::getFuncBody(const SourceManager &SM) const {
|
||||
// FIXME: Generate tooling::Replacements instead of std::string to
|
||||
// - hoist decls
|
||||
// - add return statement
|
||||
// - Add semicolon
|
||||
return toSourceCode(SM, BodyRange).str() +
|
||||
(SemicolonPolicy.isNeededInExtractedFunction() ? ";" : "");
|
||||
}
|
||||
|
||||
std::string NewFunction::Parameter::render(const DeclContext *Context) const {
|
||||
return printType(TypeInfo, *Context) + (PassByReference ? " &" : " ") + Name;
|
||||
}
|
||||
|
||||
// Stores captured information about Extraction Zone.
|
||||
struct CapturedZoneInfo {
|
||||
struct DeclInformation {
|
||||
const Decl *TheDecl;
|
||||
ZoneRelative DeclaredIn;
|
||||
// index of the declaration or first reference.
|
||||
unsigned DeclIndex;
|
||||
bool IsReferencedInZone = false;
|
||||
bool IsReferencedInPostZone = false;
|
||||
// FIXME: Capture mutation information
|
||||
DeclInformation(const Decl *TheDecl, ZoneRelative DeclaredIn,
|
||||
unsigned DeclIndex)
|
||||
: TheDecl(TheDecl), DeclaredIn(DeclaredIn), DeclIndex(DeclIndex){};
|
||||
// Marks the occurence of a reference for this declaration
|
||||
void markOccurence(ZoneRelative ReferenceLoc);
|
||||
};
|
||||
// Maps Decls to their DeclInfo
|
||||
llvm::DenseMap<const Decl *, DeclInformation> DeclInfoMap;
|
||||
// True if there is a return statement in zone.
|
||||
bool HasReturnStmt = false;
|
||||
// For now we just care whether there exists a break/continue in zone.
|
||||
bool HasBreakOrContinue = false;
|
||||
// FIXME: capture TypeAliasDecl and UsingDirectiveDecl
|
||||
// FIXME: Capture type information as well.
|
||||
DeclInformation *createDeclInfo(const Decl *D, ZoneRelative RelativeLoc);
|
||||
DeclInformation *getDeclInfoFor(const Decl *D);
|
||||
};
|
||||
|
||||
CapturedZoneInfo::DeclInformation *
|
||||
CapturedZoneInfo::createDeclInfo(const Decl *D, ZoneRelative RelativeLoc) {
|
||||
// The new Decl's index is the size of the map so far.
|
||||
auto InsertionResult = DeclInfoMap.insert(
|
||||
{D, DeclInformation(D, RelativeLoc, DeclInfoMap.size())});
|
||||
// Return the newly created DeclInfo
|
||||
return &InsertionResult.first->second;
|
||||
}
|
||||
|
||||
CapturedZoneInfo::DeclInformation *
|
||||
CapturedZoneInfo::getDeclInfoFor(const Decl *D) {
|
||||
// If the Decl doesn't exist, we
|
||||
auto Iter = DeclInfoMap.find(D);
|
||||
if (Iter == DeclInfoMap.end())
|
||||
return nullptr;
|
||||
return &Iter->second;
|
||||
}
|
||||
|
||||
void CapturedZoneInfo::DeclInformation::markOccurence(
|
||||
ZoneRelative ReferenceLoc) {
|
||||
switch (ReferenceLoc) {
|
||||
case ZoneRelative::Inside:
|
||||
IsReferencedInZone = true;
|
||||
break;
|
||||
case ZoneRelative::After:
|
||||
IsReferencedInPostZone = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Captures information from Extraction Zone
|
||||
CapturedZoneInfo captureZoneInfo(const ExtractionZone &ExtZone) {
|
||||
// We use the ASTVisitor instead of using the selection tree since we need to
|
||||
// find references in the PostZone as well.
|
||||
// FIXME: Check which statements we don't allow to extract.
|
||||
class ExtractionZoneVisitor
|
||||
: public clang::RecursiveASTVisitor<ExtractionZoneVisitor> {
|
||||
public:
|
||||
ExtractionZoneVisitor(const ExtractionZone &ExtZone) : ExtZone(ExtZone) {
|
||||
TraverseDecl(const_cast<FunctionDecl *>(ExtZone.EnclosingFunction));
|
||||
}
|
||||
bool TraverseStmt(Stmt *S) {
|
||||
bool IsRootStmt = ExtZone.isRootStmt(const_cast<const Stmt *>(S));
|
||||
// If we are starting traversal of a RootStmt, we are somewhere inside
|
||||
// ExtractionZone
|
||||
if (IsRootStmt)
|
||||
CurrentLocation = ZoneRelative::Inside;
|
||||
// Traverse using base class's TraverseStmt
|
||||
RecursiveASTVisitor::TraverseStmt(S);
|
||||
// We set the current location as after since next stmt will either be a
|
||||
// RootStmt (handled at the beginning) or after extractionZone
|
||||
if (IsRootStmt)
|
||||
CurrentLocation = ZoneRelative::After;
|
||||
return true;
|
||||
}
|
||||
bool VisitDecl(Decl *D) {
|
||||
Info.createDeclInfo(D, CurrentLocation);
|
||||
return true;
|
||||
}
|
||||
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
|
||||
// Find the corresponding Decl and mark it's occurence.
|
||||
const Decl *D = DRE->getDecl();
|
||||
auto *DeclInfo = Info.getDeclInfoFor(D);
|
||||
// If no Decl was found, the Decl must be outside the enclosingFunc.
|
||||
if (!DeclInfo)
|
||||
DeclInfo = Info.createDeclInfo(D, ZoneRelative::OutsideFunc);
|
||||
DeclInfo->markOccurence(CurrentLocation);
|
||||
// FIXME: check if reference mutates the Decl being referred.
|
||||
return true;
|
||||
}
|
||||
bool VisitReturnStmt(ReturnStmt *Return) {
|
||||
if (CurrentLocation == ZoneRelative::Inside)
|
||||
Info.HasReturnStmt = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: check for broken break/continue only.
|
||||
bool VisitBreakStmt(BreakStmt *Break) {
|
||||
if (CurrentLocation == ZoneRelative::Inside)
|
||||
Info.HasBreakOrContinue = true;
|
||||
return true;
|
||||
}
|
||||
bool VisitContinueStmt(ContinueStmt *Continue) {
|
||||
if (CurrentLocation == ZoneRelative::Inside)
|
||||
Info.HasBreakOrContinue = true;
|
||||
return true;
|
||||
}
|
||||
CapturedZoneInfo Info;
|
||||
const ExtractionZone &ExtZone;
|
||||
ZoneRelative CurrentLocation = ZoneRelative::Before;
|
||||
};
|
||||
ExtractionZoneVisitor Visitor(ExtZone);
|
||||
return std::move(Visitor.Info);
|
||||
}
|
||||
|
||||
// Adds parameters to ExtractedFunc.
|
||||
// Returns true if able to find the parameters successfully and no hoisting
|
||||
// needed.
|
||||
bool createParameters(NewFunction &ExtractedFunc,
|
||||
const CapturedZoneInfo &CapturedInfo) {
|
||||
for (const auto &KeyVal : CapturedInfo.DeclInfoMap) {
|
||||
const auto &DeclInfo = KeyVal.second;
|
||||
// If a Decl was Declared in zone and referenced in post zone, it
|
||||
// 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)
|
||||
continue; // no need to pass as parameter, not referenced
|
||||
if (DeclInfo.DeclaredIn == ZoneRelative::Inside ||
|
||||
DeclInfo.DeclaredIn == ZoneRelative::OutsideFunc)
|
||||
continue; // no need to pass as parameter, still accessible.
|
||||
// Parameter specific checks.
|
||||
const ValueDecl *VD = dyn_cast_or_null<ValueDecl>(DeclInfo.TheDecl);
|
||||
// Can't parameterise if the Decl isn't a ValueDecl or is a FunctionDecl
|
||||
// (this includes the case of recursive call to EnclosingFunc in Zone).
|
||||
if (!VD || isa<FunctionDecl>(DeclInfo.TheDecl))
|
||||
return false;
|
||||
// Parameter qualifiers are same as the Decl's qualifiers.
|
||||
QualType TypeInfo = VD->getType().getNonReferenceType();
|
||||
// FIXME: Need better qualifier checks: check mutated status for
|
||||
// Decl(e.g. was it assigned, passed as nonconst argument, etc)
|
||||
// FIXME: check if parameter will be a non l-value reference.
|
||||
// FIXME: We don't want to always pass variables of types like int,
|
||||
// pointers, etc by reference.
|
||||
bool IsPassedByReference = true;
|
||||
// We use the index of declaration as the ordering priority for parameters.
|
||||
ExtractedFunc.Parameters.push_back(
|
||||
{VD->getName(), TypeInfo, IsPassedByReference, DeclInfo.DeclIndex});
|
||||
}
|
||||
llvm::sort(ExtractedFunc.Parameters);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clangd uses open ranges while ExtractionSemicolonPolicy (in Clang Tooling)
|
||||
// uses closed ranges. Generates the semicolon policy for the extraction and
|
||||
// extends the ZoneRange if necessary.
|
||||
tooling::ExtractionSemicolonPolicy
|
||||
getSemicolonPolicy(ExtractionZone &ExtZone, const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
// Get closed ZoneRange.
|
||||
SourceRange FuncBodyRange = {ExtZone.ZoneRange.getBegin(),
|
||||
ExtZone.ZoneRange.getEnd().getLocWithOffset(-1)};
|
||||
auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute(
|
||||
ExtZone.getLastRootStmt()->ASTNode.get<Stmt>(), FuncBodyRange, SM,
|
||||
LangOpts);
|
||||
// Update ZoneRange.
|
||||
ExtZone.ZoneRange.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1));
|
||||
return SemicolonPolicy;
|
||||
}
|
||||
|
||||
// Generate return type for ExtractedFunc. Return false if unable to do so.
|
||||
bool generateReturnProperties(NewFunction &ExtractedFunc,
|
||||
const CapturedZoneInfo &CapturedInfo) {
|
||||
|
||||
// FIXME: Use Existing Return statements (if present)
|
||||
// FIXME: Generate new return statement if needed.
|
||||
if (CapturedInfo.HasReturnStmt)
|
||||
return false;
|
||||
ExtractedFunc.ReturnType = "void";
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME: add support for adding other function return types besides void.
|
||||
// FIXME: assign the value returned by non void extracted function.
|
||||
llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
CapturedZoneInfo CapturedInfo = captureZoneInfo(ExtZone);
|
||||
// Bail out if any break of continue exists
|
||||
// FIXME: check for broken control flow only
|
||||
if (CapturedInfo.HasBreakOrContinue)
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
+"Cannot extract break or continue.");
|
||||
NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts));
|
||||
ExtractedFunc.BodyRange = ExtZone.ZoneRange;
|
||||
ExtractedFunc.InsertionPoint = ExtZone.getInsertionPoint();
|
||||
ExtractedFunc.EnclosingFuncContext =
|
||||
ExtZone.EnclosingFunction->getDeclContext();
|
||||
if (!createParameters(ExtractedFunc, CapturedInfo) ||
|
||||
!generateReturnProperties(ExtractedFunc, CapturedInfo))
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
+"Too complex to extract.");
|
||||
return ExtractedFunc;
|
||||
}
|
||||
|
||||
class ExtractFunction : public Tweak {
|
||||
public:
|
||||
const char *id() const override final;
|
||||
bool prepare(const Selection &Inputs) override;
|
||||
Expected<Effect> apply(const Selection &Inputs) override;
|
||||
std::string title() const override { return "Extract to function"; }
|
||||
Intent intent() const override { return Refactor; }
|
||||
|
||||
private:
|
||||
ExtractionZone ExtZone;
|
||||
};
|
||||
|
||||
REGISTER_TWEAK(ExtractFunction)
|
||||
tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc,
|
||||
const SourceManager &SM,
|
||||
const LangOptions &LangOpts) {
|
||||
std::string FuncCall = ExtractedFunc.renderCall();
|
||||
return tooling::Replacement(
|
||||
SM, CharSourceRange(ExtractedFunc.BodyRange, false), FuncCall, LangOpts);
|
||||
}
|
||||
|
||||
tooling::Replacement createFunctionDefinition(const NewFunction &ExtractedFunc,
|
||||
const SourceManager &SM) {
|
||||
std::string FunctionDef = ExtractedFunc.renderDefinition(SM);
|
||||
return tooling::Replacement(SM, ExtractedFunc.InsertionPoint, 0, FunctionDef);
|
||||
}
|
||||
|
||||
bool ExtractFunction::prepare(const Selection &Inputs) {
|
||||
const Node *CommonAnc = Inputs.ASTSelection.commonAncestor();
|
||||
const SourceManager &SM = Inputs.AST.getSourceManager();
|
||||
const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts();
|
||||
if (auto MaybeExtZone = findExtractionZone(CommonAnc, SM, LangOpts)) {
|
||||
ExtZone = std::move(*MaybeExtZone);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {
|
||||
const SourceManager &SM = Inputs.AST.getSourceManager();
|
||||
const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts();
|
||||
auto ExtractedFunc = getExtractedFunction(ExtZone, SM, LangOpts);
|
||||
// FIXME: Add more types of errors.
|
||||
if (!ExtractedFunc)
|
||||
return ExtractedFunc.takeError();
|
||||
tooling::Replacements Result;
|
||||
if (auto Err = Result.add(createFunctionDefinition(*ExtractedFunc, SM)))
|
||||
return std::move(Err);
|
||||
if (auto Err = Result.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts)))
|
||||
return std::move(Err);
|
||||
return Effect::applyEdit(Result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
|
@ -500,6 +500,112 @@ TEST_F(ExpandAutoTypeTest, Test) {
|
|||
R"cpp(const char * x = "test")cpp");
|
||||
}
|
||||
|
||||
TWEAK_TEST(ExtractFunction);
|
||||
TEST_F(ExtractFunctionTest, FunctionTest) {
|
||||
Context = Function;
|
||||
|
||||
// Root statements should have common parent.
|
||||
EXPECT_EQ(apply("for(;;) [[1+2; 1+2;]]"), "unavailable");
|
||||
// Expressions aren't extracted.
|
||||
EXPECT_EQ(apply("int x = 0; [[x++;]]"), "unavailable");
|
||||
// We don't support extraction from lambdas.
|
||||
EXPECT_EQ(apply("auto lam = [](){ [[int x;]] }; "), "unavailable");
|
||||
|
||||
// Ensure that end of Zone and Beginning of PostZone being adjacent doesn't
|
||||
// lead to break being included in the extraction zone.
|
||||
EXPECT_THAT(apply("for(;;) { [[int x;]]break; }"), HasSubstr("extracted"));
|
||||
// FIXME: This should be unavailable since partially selected but
|
||||
// selectionTree doesn't always work correctly for VarDecls.
|
||||
EXPECT_THAT(apply("int [[x = 0]];"), HasSubstr("extracted"));
|
||||
// FIXME: ExtractFunction should be unavailable inside loop construct
|
||||
// initalizer/condition.
|
||||
EXPECT_THAT(apply(" for([[int i = 0;]];);"), HasSubstr("extracted"));
|
||||
// Don't extract because needs hoisting.
|
||||
EXPECT_THAT(apply(" [[int a = 5;]] a++; "), StartsWith("fail"));
|
||||
// Don't extract return
|
||||
EXPECT_THAT(apply(" if(true) [[return;]] "), StartsWith("fail"));
|
||||
// Don't extract break and continue.
|
||||
// FIXME: We should be able to extract this since it's non broken.
|
||||
EXPECT_THAT(apply(" [[for(;;) break;]] "), StartsWith("fail"));
|
||||
EXPECT_THAT(apply(" for(;;) [[continue;]] "), StartsWith("fail"));
|
||||
}
|
||||
|
||||
TEST_F(ExtractFunctionTest, FileTest) {
|
||||
// Check all parameters are in order
|
||||
std::string ParameterCheckInput = R"cpp(
|
||||
struct Foo {
|
||||
int x;
|
||||
};
|
||||
void f(int a) {
|
||||
int b;
|
||||
int *ptr = &a;
|
||||
Foo foo;
|
||||
[[a += foo.x + b;
|
||||
*ptr++;]]
|
||||
})cpp";
|
||||
std::string ParameterCheckOutput = R"cpp(
|
||||
struct Foo {
|
||||
int x;
|
||||
};
|
||||
void extracted(int &a, int &b, int * &ptr, Foo &foo) {
|
||||
a += foo.x + b;
|
||||
*ptr++;
|
||||
}
|
||||
void f(int a) {
|
||||
int b;
|
||||
int *ptr = &a;
|
||||
Foo foo;
|
||||
extracted(a, b, ptr, foo);
|
||||
})cpp";
|
||||
EXPECT_EQ(apply(ParameterCheckInput), ParameterCheckOutput);
|
||||
|
||||
// Check const qualifier
|
||||
std::string ConstCheckInput = R"cpp(
|
||||
void f(const int c) {
|
||||
[[while(c) {}]]
|
||||
})cpp";
|
||||
std::string ConstCheckOutput = R"cpp(
|
||||
void extracted(const int &c) {
|
||||
while(c) {}
|
||||
}
|
||||
void f(const int c) {
|
||||
extracted(c);
|
||||
})cpp";
|
||||
EXPECT_EQ(apply(ConstCheckInput), ConstCheckOutput);
|
||||
|
||||
// Don't extract when we need to make a function as a parameter.
|
||||
EXPECT_THAT(apply("void f() { [[int a; f();]] }"), StartsWith("fail"));
|
||||
|
||||
// We don't extract from methods for now since they may involve multi-file
|
||||
// edits
|
||||
std::string MethodFailInput = R"cpp(
|
||||
class T {
|
||||
void f() {
|
||||
[[int x;]]
|
||||
}
|
||||
};
|
||||
)cpp";
|
||||
EXPECT_EQ(apply(MethodFailInput), "unavailable");
|
||||
|
||||
// We don't extract from templated functions for now as templates are hard
|
||||
// to deal with.
|
||||
std::string TemplateFailInput = R"cpp(
|
||||
template<typename T>
|
||||
void f() {
|
||||
[[int x;]]
|
||||
}
|
||||
)cpp";
|
||||
EXPECT_EQ(apply(TemplateFailInput), "unavailable");
|
||||
|
||||
// FIXME: This should be extractable after selectionTree works correctly for
|
||||
// macros (currently it doesn't select anything for the following case)
|
||||
std::string MacroFailInput = R"cpp(
|
||||
#define F(BODY) void f() { BODY }
|
||||
F ([[int x = 0;]])
|
||||
)cpp";
|
||||
EXPECT_EQ(apply(MacroFailInput), "unavailable");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
Loading…
Reference in New Issue