[clang-tools-extra] r366869 - [Clangd] Fixed ExtractVariable for certain types of Exprs
Shaurya Gupta via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 23 22:42:56 PDT 2019
Author: sureyeaah
Date: Tue Jul 23 22:42:55 2019
New Revision: 366869
URL: http://llvm.org/viewvc/llvm-project?rev=366869&view=rev
Log:
[Clangd] Fixed ExtractVariable for certain types of Exprs
Summary:
- Modified ExtractVariable for extraction of MemberExpr, DeclRefExpr and Assignment Expr
- Removed extraction from label statements.
- Fixed unittests
Reviewers: sammccall, kadircet
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D64717
Modified:
clang-tools-extra/trunk/clangd/refactor/tweaks/ExtractVariable.cpp
clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp
Modified: clang-tools-extra/trunk/clangd/refactor/tweaks/ExtractVariable.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/refactor/tweaks/ExtractVariable.cpp?rev=366869&r1=366868&r2=366869&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/refactor/tweaks/ExtractVariable.cpp (original)
+++ clang-tools-extra/trunk/clangd/refactor/tweaks/ExtractVariable.cpp Tue Jul 23 22:42:55 2019
@@ -13,6 +13,7 @@
#include "refactor/Tweak.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
@@ -77,29 +78,15 @@ computeReferencedDecls(const clang::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().getTypePtrOrNull();
- // 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;
- }
+ ReferencedDecls = computeReferencedDecls(Expr);
+ InsertionPoint = computeInsertionPoint();
+ if (InsertionPoint)
+ Extractable = true;
}
// checks whether extracting before InsertionPoint will take a
@@ -121,9 +108,9 @@ bool ExtractionContext::exprIsValidOutsi
// the current Stmt. We ALWAYS insert before a Stmt whose parent is a
// CompoundStmt
//
-
-// FIXME: Extraction from switch and case statements
+// FIXME: Extraction from label, switch and case statements
// FIXME: Doens't work for FoldExpr
+// FIXME: Ensure extraction from loops doesn't change semantics.
const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
// returns true if we can extract before InsertionPoint
auto CanExtractOutside =
@@ -141,8 +128,7 @@ const clang::Stmt *ExtractionContext::co
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);
+ isa<ReturnStmt>(Stmt) || isa<WhileStmt>(Stmt);
}
if (InsertionPoint->ASTNode.get<VarDecl>())
return true;
@@ -209,6 +195,9 @@ public:
return "Extract subexpression to variable";
}
Intent intent() const override { return Refactor; }
+ // Compute the extraction context for the Selection
+ bool computeExtractionContext(const SelectionTree::Node *N,
+ const SourceManager &SM, const ASTContext &Ctx);
private:
// the expression to extract
@@ -216,14 +205,13 @@ private:
};
REGISTER_TWEAK(ExtractVariable)
bool ExtractVariable::prepare(const Selection &Inputs) {
+ // we don't trigger on empty selections for now
+ if (Inputs.SelectionBegin == Inputs.SelectionEnd)
+ return false;
const ASTContext &Ctx = Inputs.AST.getASTContext();
const SourceManager &SM = Inputs.AST.getSourceManager();
const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
- // we don't trigger on empty selections for now
- if (!N || Inputs.SelectionBegin == Inputs.SelectionEnd)
- return false;
- Target = llvm::make_unique<ExtractionContext>(N, SM, Ctx);
- return Target->isExtractable();
+ return computeExtractionContext(N, SM, Ctx);
}
Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
@@ -239,6 +227,75 @@ Expected<Tweak::Effect> ExtractVariable:
return Effect::applyEdit(Result);
}
+// Find the CallExpr whose callee is an ancestor of the DeclRef
+const SelectionTree::Node *getCallExpr(const SelectionTree::Node *DeclRef) {
+ // we maintain a stack of all exprs encountered while traversing the
+ // selectiontree because the callee of the callexpr can be an ancestor of the
+ // DeclRef. e.g. Callee can be an ImplicitCastExpr.
+ std::vector<const clang::Expr *> ExprStack;
+ for (auto *CurNode = DeclRef; CurNode; CurNode = CurNode->Parent) {
+ const Expr *CurExpr = CurNode->ASTNode.get<Expr>();
+ if (const CallExpr *CallPar = CurNode->ASTNode.get<CallExpr>()) {
+ // check whether the callee of the callexpr is present in Expr stack.
+ if (std::find(ExprStack.begin(), ExprStack.end(), CallPar->getCallee()) !=
+ ExprStack.end())
+ return CurNode;
+ return nullptr;
+ }
+ ExprStack.push_back(CurExpr);
+ }
+ return nullptr;
+}
+
+// check if Expr can be assigned to a variable i.e. is non-void type
+bool canBeAssigned(const SelectionTree::Node *ExprNode) {
+ const clang::Expr *Expr = ExprNode->ASTNode.get<clang::Expr>();
+ if (const Type *ExprType = Expr->getType().getTypePtrOrNull())
+ // FIXME: check if we need to cover any other types
+ return !ExprType->isVoidType();
+ return true;
+}
+
+// Find the node that will form our ExtractionContext.
+// We don't want to trigger for assignment expressions and variable/field
+// DeclRefs. For function/member function, we want to extract the entire
+// function call.
+bool ExtractVariable::computeExtractionContext(const SelectionTree::Node *N,
+ const SourceManager &SM,
+ const ASTContext &Ctx) {
+ if (!N)
+ return false;
+ const clang::Expr *SelectedExpr = N->ASTNode.get<clang::Expr>();
+ const SelectionTree::Node *TargetNode = N;
+ if (!SelectedExpr)
+ return false;
+ // Extracting Exprs like a = 1 gives dummy = a = 1 which isn't useful.
+ if (const BinaryOperator *BinOpExpr =
+ dyn_cast_or_null<BinaryOperator>(SelectedExpr)) {
+ if (BinOpExpr->getOpcode() == BinaryOperatorKind::BO_Assign)
+ return false;
+ }
+ // For function and member function DeclRefs, we look for a parent that is a
+ // CallExpr
+ if (const DeclRefExpr *DeclRef =
+ dyn_cast_or_null<DeclRefExpr>(SelectedExpr)) {
+ // Extracting just a variable isn't that useful.
+ if (!isa<FunctionDecl>(DeclRef->getDecl()))
+ return false;
+ TargetNode = getCallExpr(N);
+ }
+ if (const MemberExpr *Member = dyn_cast_or_null<MemberExpr>(SelectedExpr)) {
+ // Extracting just a field member isn't that useful.
+ if (!isa<CXXMethodDecl>(Member->getMemberDecl()))
+ return false;
+ TargetNode = getCallExpr(N);
+ }
+ if (!TargetNode || !canBeAssigned(TargetNode))
+ return false;
+ Target = llvm::make_unique<ExtractionContext>(TargetNode, SM, Ctx);
+ return Target->isExtractable();
+}
+
} // namespace
} // namespace clangd
} // namespace clang
Modified: clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp?rev=366869&r1=366868&r2=366869&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp (original)
+++ clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp Tue Jul 23 22:42:55 2019
@@ -291,18 +291,23 @@ 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() {
+ int xyz(int a = 1) {
+ struct T {
+ int bar(int a = 1);
+ int z;
+ } t;
// return statement
- return [[1]];
+ return [[[[t.b[[a]]r]](t.z)]];
}
void f() {
int a = [[5 +]] [[4 * [[[[xyz]]()]]]];
// multivariable initialization
if(1)
- int x = [[1]], y = [[a]] + 1, a = [[1]], z = a + 1;
+ int x = [[1]], y = [[a + 1]], a = [[1]], z = a + 1;
// if without else
if([[1]])
a = [[1]];
@@ -316,13 +321,13 @@ TEST(TweakTest, ExtractVariable) {
a = [[4]];
else
a = [[5]];
- // for loop
+ // for loop
for(a = [[1]]; a > [[[[3]] + [[4]]]]; a++)
a = [[2]];
- // while
+ // while
while(a < [[1]])
- [[a]]++;
- // do while
+ [[a++]];
+ // do while
do
a = [[1]];
while(a < [[3]]);
@@ -332,18 +337,19 @@ TEST(TweakTest, ExtractVariable) {
checkNotAvailable(ID, R"cpp(
template<typename T, typename ...Args>
struct Test<T, Args...> {
- Test(const T &v) :val(^) {}
+ Test(const T &v) :val[[(^]]) {}
T val;
};
)cpp");
checkNotAvailable(ID, R"cpp(
int xyz(int a = [[1]]) {
- return 1;
- class T {
- T(int a = [[1]]) {};
- int xyz = [[1]];
- };
+ struct T {
+ int bar(int a = [[1]]);
+ int z = [[1]];
+ } t;
+ return [[t]].bar([[[[t]].z]]);
}
+ void v() { return; }
// function default argument
void f(int b = [[1]]) {
// empty selection
@@ -351,17 +357,26 @@ TEST(TweakTest, ExtractVariable) {
// void expressions
auto i = new int, j = new int;
[[[[delete i]], delete j]];
+ [[v]]();
// if
if(1)
int x = 1, y = a + 1, a = 1, z = [[a + 1]];
if(int a = 1)
- if([[a]] == 4)
+ if([[a + 1]] == 4)
a = [[[[a]] +]] 1;
- // for loop
- for(int a = 1, b = 2, c = 3; [[a]] > [[b + c]]; [[a]]++)
+ // for loop
+ for(int a = 1, b = 2, c = 3; a > [[b + c]]; [[a++]])
a = [[a + 1]];
- // lambda
+ // lambda
auto lamb = [&[[a]], &[[b]]](int r = [[1]]) {return 1;}
+ // assigment
+ [[a = 5]];
+ // Variable DeclRefExpr
+ a = [[b]];
+ // label statement
+ goto label;
+ label:
+ a = [[1]];
}
)cpp");
// vector of pairs of input and output strings
@@ -397,6 +412,15 @@ TEST(TweakTest, ExtractVariable) {
break;
}
})cpp"},*/
+ // Macros
+ {R"cpp(#define PLUS(x) x++
+ void f(int a) {
+ PLUS([[1+a]]);
+ })cpp",
+ R"cpp(#define PLUS(x) x++
+ void f(int a) {
+ auto dummy = PLUS(1+a); dummy;
+ })cpp"},
// ensure InsertionPoint isn't inside a macro
{R"cpp(#define LOOP(x) while (1) {a = x;}
void f(int a) {
@@ -425,25 +449,35 @@ TEST(TweakTest, ExtractVariable) {
auto dummy = 3; if(1)
LOOP(5 + dummy)
})cpp"},
- // label and attribute testing
+ // attribute testing
{R"cpp(void f(int a) {
- label: [ [gsl::suppress("type")] ] for (;;) a = [[1]];
+ [ [gsl::suppress("type")] ] for (;;) a = [[1]];
})cpp",
R"cpp(void f(int a) {
- auto dummy = 1; label: [ [gsl::suppress("type")] ] for (;;) a = dummy;
+ auto dummy = 1; [ [gsl::suppress("type")] ] for (;;) a = dummy;
})cpp"},
- // macro testing
- {R"cpp(#define PLUS(x) x++
- void f(int a) {
- PLUS([[a]]);
+ // MemberExpr
+ {R"cpp(class T {
+ T f() {
+ return [[T().f()]].f();
+ }
+ };)cpp",
+ R"cpp(class T {
+ T f() {
+ auto dummy = T().f(); return dummy.f();
+ }
+ };)cpp"},
+ // Function DeclRefExpr
+ {R"cpp(int f() {
+ return [[f]]();
})cpp",
- R"cpp(#define PLUS(x) x++
- void f(int a) {
- auto dummy = a; PLUS(dummy);
+ R"cpp(int f() {
+ auto dummy = f(); return 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
+
+ // FIXME: Wrong result 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);
More information about the cfe-commits
mailing list