dummy variable extraction on a function scope

Summary:
- Added extraction to a dummy variable
- using auto for the dummy variable type for now
- Works on a function scope
- Adding braces to create a compound statement not supported yet
- added unit tests

Reviewers: sammccall, kadircet

Subscribers: mgorny, jkorous, arphaman, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D63773

llvm-svn: 365453
This commit is contained in:
Shaurya Gupta 2019-07-09 10:12:26 +00:00
parent 456fc4fa6d
commit 1fb97c9036
3 changed files with 380 additions and 6 deletions

View File

@ -17,6 +17,7 @@ add_clang_library(clangDaemonTweaks OBJECT
ExpandMacro.cpp
RawStringLiteral.cpp
SwapIfBranches.cpp
ExtractVariable.cpp
LINK_LIBS
clangAST

View File

@ -0,0 +1,243 @@
//===--- ExtractVariable.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
//
//===----------------------------------------------------------------------===//
#include "ClangdUnit.h"
#include "Logger.h"
#include "Protocol.h"
#include "Selection.h"
#include "SourceCode.h"
#include "refactor/Tweak.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
namespace clang {
namespace clangd {
namespace {
// information regarding the Expr that is being extracted
class ExtractionContext {
public:
ExtractionContext(const SelectionTree::Node *Node, const SourceManager &SM,
const ASTContext &Ctx);
const clang::Expr *getExpr() const { return Expr; }
const SelectionTree::Node *getExprNode() const { return ExprNode; }
bool isExtractable() const { return Extractable; }
// Generate Replacement for replacing selected expression with given VarName
tooling::Replacement replaceWithVar(llvm::StringRef VarName) const;
// Generate Replacement for declaring the selected Expr as a new variable
tooling::Replacement insertDeclaration(llvm::StringRef VarName) const;
private:
bool Extractable = false;
const clang::Expr *Expr;
const SelectionTree::Node *ExprNode;
// Stmt before which we will extract
const clang::Stmt *InsertionPoint = nullptr;
const SourceManager &SM;
const ASTContext &Ctx;
// Decls referenced in the Expr
std::vector<clang::Decl *> ReferencedDecls;
// returns true if the Expr doesn't reference any variable declared in scope
bool exprIsValidOutside(const clang::Stmt *Scope) const;
// computes the Stmt before which we will extract out Expr
const clang::Stmt *computeInsertionPoint() const;
};
// Returns all the Decls referenced inside the given Expr
static std::vector<clang::Decl *>
computeReferencedDecls(const clang::Expr *Expr) {
// RAV subclass to find all DeclRefs in a given Stmt
class FindDeclRefsVisitor
: public clang::RecursiveASTVisitor<FindDeclRefsVisitor> {
public:
std::vector<Decl *> ReferencedDecls;
bool VisitDeclRefExpr(DeclRefExpr *DeclRef) { // NOLINT
ReferencedDecls.push_back(DeclRef->getDecl());
return true;
}
};
FindDeclRefsVisitor Visitor;
Visitor.TraverseStmt(const_cast<Stmt *>(dyn_cast<Stmt>(Expr)));
return Visitor.ReferencedDecls;
}
// An expr is not extractable if it's null or an expression of type void
// FIXME: Ignore assignment (a = 1) Expr since it is extracted as dummy = a =
static bool isExtractableExpr(const clang::Expr *Expr) {
if (Expr) {
const Type *ExprType = Expr->getType().getTypePtr();
// FIXME: check if we need to cover any other types
if (ExprType)
return !ExprType->isVoidType();
}
return false;
}
ExtractionContext::ExtractionContext(const SelectionTree::Node *Node,
const SourceManager &SM,
const ASTContext &Ctx)
: ExprNode(Node), SM(SM), Ctx(Ctx) {
Expr = Node->ASTNode.get<clang::Expr>();
if (isExtractableExpr(Expr)) {
ReferencedDecls = computeReferencedDecls(Expr);
InsertionPoint = computeInsertionPoint();
if (InsertionPoint)
Extractable = true;
}
}
// checks whether extracting before InsertionPoint will take a
// variable reference out of scope
bool ExtractionContext::exprIsValidOutside(const clang::Stmt *Scope) const {
SourceLocation ScopeBegin = Scope->getBeginLoc();
SourceLocation ScopeEnd = Scope->getEndLoc();
for (const Decl *ReferencedDecl : ReferencedDecls) {
if (SM.isPointWithin(ReferencedDecl->getBeginLoc(), ScopeBegin, ScopeEnd) &&
SM.isPointWithin(ReferencedDecl->getEndLoc(), ScopeBegin, ScopeEnd))
return false;
}
return true;
}
// Return the Stmt before which we need to insert the extraction.
// To find the Stmt, we go up the AST Tree and if the Parent of the current
// Stmt is a CompoundStmt, we can extract inside this CompoundStmt just before
// the current Stmt. We ALWAYS insert before a Stmt whose parent is a
// CompoundStmt
//
// FIXME: Extraction from switch and case statements
// FIXME: Doens't work for FoldExpr
const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
// returns true if we can extract before InsertionPoint
auto CanExtractOutside =
[](const SelectionTree::Node *InsertionPoint) -> bool {
if (const clang::Stmt *Stmt = InsertionPoint->ASTNode.get<clang::Stmt>()) {
// Allow all expressions except LambdaExpr since we don't want to extract
// from the captures/default arguments of a lambda
if (isa<clang::Expr>(Stmt))
return !isa<LambdaExpr>(Stmt);
// We don't yet allow extraction from switch/case stmt as we would need to
// jump over the switch stmt even if there is a CompoundStmt inside the
// switch. And there are other Stmts which we don't care about (e.g.
// continue and break) as there can never be anything to extract from
// them.
return isa<AttributedStmt>(Stmt) || isa<CompoundStmt>(Stmt) ||
isa<CXXForRangeStmt>(Stmt) || isa<DeclStmt>(Stmt) ||
isa<DoStmt>(Stmt) || isa<ForStmt>(Stmt) || isa<IfStmt>(Stmt) ||
isa<LabelStmt>(Stmt) || isa<ReturnStmt>(Stmt) ||
isa<WhileStmt>(Stmt);
}
if (InsertionPoint->ASTNode.get<VarDecl>())
return true;
return false;
};
for (const SelectionTree::Node *CurNode = getExprNode();
CurNode->Parent && CanExtractOutside(CurNode);
CurNode = CurNode->Parent) {
const clang::Stmt *CurInsertionPoint = CurNode->ASTNode.get<Stmt>();
// give up if extraction will take a variable out of scope
if (CurInsertionPoint && !exprIsValidOutside(CurInsertionPoint))
break;
if (const clang::Stmt *CurParent = CurNode->Parent->ASTNode.get<Stmt>()) {
if (isa<CompoundStmt>(CurParent)) {
// Ensure we don't write inside a macro.
if (CurParent->getBeginLoc().isMacroID())
continue;
return CurInsertionPoint;
}
}
}
return nullptr;
}
// returns the replacement for substituting the extraction with VarName
tooling::Replacement
ExtractionContext::replaceWithVar(llvm::StringRef VarName) const {
const llvm::Optional<SourceRange> ExtractionRng =
toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
unsigned ExtractionLength = SM.getFileOffset(ExtractionRng->getEnd()) -
SM.getFileOffset(ExtractionRng->getBegin());
return tooling::Replacement(SM, ExtractionRng->getBegin(), ExtractionLength,
VarName);
}
// returns the Replacement for declaring a new variable storing the extraction
tooling::Replacement
ExtractionContext::insertDeclaration(llvm::StringRef VarName) const {
const llvm::Optional<SourceRange> ExtractionRng =
toHalfOpenFileRange(SM, Ctx.getLangOpts(), getExpr()->getSourceRange());
assert(ExractionRng && "ExtractionRng should not be null");
llvm::StringRef ExtractionCode = toSourceCode(SM, *ExtractionRng);
const SourceLocation InsertionLoc =
toHalfOpenFileRange(SM, Ctx.getLangOpts(),
InsertionPoint->getSourceRange())
->getBegin();
// FIXME: Replace auto with explicit type and add &/&& as necessary
std::string ExtractedVarDecl = std::string("auto ") + VarName.str() + " = " +
ExtractionCode.str() + "; ";
return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl);
}
/// Extracts an expression to the variable dummy
/// Before:
/// int x = 5 + 4 * 3;
/// ^^^^^
/// After:
/// auto dummy = 5 + 4;
/// int x = dummy * 3;
class ExtractVariable : 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 subexpression to variable";
}
Intent intent() const override { return Refactor; }
private:
// the expression to extract
std::unique_ptr<ExtractionContext> Target;
};
REGISTER_TWEAK(ExtractVariable)
bool ExtractVariable::prepare(const Selection &Inputs) {
const ASTContext &Ctx = Inputs.AST.getASTContext();
const SourceManager &SM = Inputs.AST.getSourceManager();
const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
if (!N)
return false;
Target = llvm::make_unique<ExtractionContext>(N, SM, Ctx);
return Target->isExtractable();
}
Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
tooling::Replacements Result;
// FIXME: get variable name from user or suggest based on type
std::string VarName = "dummy";
// insert new variable declaration
if (auto Err = Result.add(Target->insertDeclaration(VarName)))
return std::move(Err);
// replace expression with variable name
if (auto Err = Result.add(Target->replaceWithVar(VarName)))
return std::move(Err);
return Effect::applyEdit(Result);
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@ -239,13 +239,13 @@ TEST(TweakTest, DumpAST) {
checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
const char *Input = "int x = 2 ^+ 2;";
auto result = getMessage(ID, Input);
EXPECT_THAT(result, ::testing::HasSubstr("BinaryOperator"));
EXPECT_THAT(result, ::testing::HasSubstr("'+'"));
EXPECT_THAT(result, ::testing::HasSubstr("|-IntegerLiteral"));
EXPECT_THAT(result,
auto Result = getMessage(ID, Input);
EXPECT_THAT(Result, ::testing::HasSubstr("BinaryOperator"));
EXPECT_THAT(Result, ::testing::HasSubstr("'+'"));
EXPECT_THAT(Result, ::testing::HasSubstr("|-IntegerLiteral"));
EXPECT_THAT(Result,
::testing::HasSubstr("<col:9> 'int' 2\n`-IntegerLiteral"));
EXPECT_THAT(result, ::testing::HasSubstr("<col:13> 'int' 2"));
EXPECT_THAT(Result, ::testing::HasSubstr("<col:13> 'int' 2"));
}
TEST(TweakTest, ShowSelectionTree) {
@ -277,6 +277,136 @@ TEST(TweakTest, DumpRecordLayout) {
const char *Input = "struct ^X { int x; int y; }";
EXPECT_THAT(getMessage(ID, Input), ::testing::HasSubstr("0 | int x"));
}
TEST(TweakTest, ExtractVariable) {
llvm::StringLiteral ID = "ExtractVariable";
checkAvailable(ID, R"cpp(
int xyz() {
// return statement
return ^1;
}
void f() {
int a = 5 + [[4 ^* ^xyz^()]];
// multivariable initialization
if(1)
int x = ^1, y = ^a + 1, a = ^1, z = a + 1;
// if without else
if(^1) {}
// if with else
if(a < ^3)
if(a == ^4)
a = ^5;
else
a = ^6;
else if (a < ^4)
a = ^4;
else
a = ^5;
// for loop
for(a = ^1; a > ^3^+^4; a++)
a = ^2;
// while
while(a < ^1)
^a++;
// do while
do
a = ^1;
while(a < ^3);
}
)cpp");
checkNotAvailable(ID, R"cpp(
int xyz(int a = ^1) {
return 1;
class T {
T(int a = ^1) {};
int xyz = ^1;
};
}
// function default argument
void f(int b = ^1) {
// void expressions
auto i = new int, j = new int;
de^lete i^, del^ete j;
// if
if(1)
int x = 1, y = a + 1, a = 1, z = ^a + 1;
if(int a = 1)
if(^a == 4)
a = ^a ^+ 1;
// for loop
for(int a = 1, b = 2, c = 3; ^a > ^b ^+ ^c; ^a++)
a = ^a ^+ 1;
// lambda
auto lamb = [&^a, &^b](int r = ^1) {return 1;}
}
)cpp");
// vector of pairs of input and output strings
const std::vector<std::pair<llvm::StringLiteral, llvm::StringLiteral>>
InputOutputs = {
// extraction from variable declaration/assignment
{R"cpp(void varDecl() {
int a = 5 * (4 + (3 [[- 1)]]);
})cpp",
R"cpp(void varDecl() {
auto dummy = (3 - 1); int a = 5 * (4 + dummy);
})cpp"},
// FIXME: extraction from switch case
/*{R"cpp(void f(int a) {
if(1)
while(a < 1)
switch (1) {
case 1:
a = [[1 + 2]];
break;
default:
break;
}
})cpp",
R"cpp(void f(int a) {
auto dummy = 1 + 2; if(1)
while(a < 1)
switch (1) {
case 1:
a = dummy;
break;
default:
break;
}
})cpp"},*/
// ensure InsertionPoint isn't inside a macro
{R"cpp(#define LOOP(x) {int a = x + 1;}
void f(int a) {
if(1)
LOOP(5 + ^3)
})cpp",
R"cpp(#define LOOP(x) {int a = x + 1;}
void f(int a) {
auto dummy = 3; if(1)
LOOP(5 + dummy)
})cpp"},
// label and attribute testing
{R"cpp(void f(int a) {
label: [ [gsl::suppress("type")] ] for (;;) a = ^1;
})cpp",
R"cpp(void f(int a) {
auto dummy = 1; label: [ [gsl::suppress("type")] ] for (;;) a = dummy;
})cpp"},
// FIXME: Doesn't work because bug in selection tree
/*{R"cpp(#define PLUS(x) x++
void f(int a) {
PLUS(^a);
})cpp",
R"cpp(#define PLUS(x) x++
void f(int a) {
auto dummy = a; PLUS(dummy);
})cpp"},*/
// FIXME: Doesn't work correctly for \[\[clang::uninitialized\]\] int b
// = 1; since the attr is inside the DeclStmt and the bounds of
// DeclStmt don't cover the attribute
};
for (const auto &IO : InputOutputs) {
checkTransform(ID, IO.first, IO.second);
}
}
TEST(TweakTest, AnnotateHighlightings) {
llvm::StringLiteral ID = "AnnotateHighlightings";