13
13
#include " refactor/Tweak.h"
14
14
#include " clang/AST/ASTContext.h"
15
15
#include " clang/AST/Expr.h"
16
+ #include " clang/AST/ExprCXX.h"
16
17
#include " clang/AST/OperationKinds.h"
17
18
#include " clang/AST/RecursiveASTVisitor.h"
18
19
#include " clang/AST/Stmt.h"
@@ -77,29 +78,15 @@ computeReferencedDecls(const clang::Expr *Expr) {
77
78
return Visitor.ReferencedDecls ;
78
79
}
79
80
80
- // An expr is not extractable if it's null or an expression of type void
81
- // FIXME: Ignore assignment (a = 1) Expr since it is extracted as dummy = a =
82
- static bool isExtractableExpr (const clang::Expr *Expr) {
83
- if (Expr) {
84
- const Type *ExprType = Expr->getType ().getTypePtrOrNull ();
85
- // FIXME: check if we need to cover any other types
86
- if (ExprType)
87
- return !ExprType->isVoidType ();
88
- }
89
- return false ;
90
- }
91
-
92
81
ExtractionContext::ExtractionContext (const SelectionTree::Node *Node,
93
82
const SourceManager &SM,
94
83
const ASTContext &Ctx)
95
84
: ExprNode(Node), SM(SM), Ctx(Ctx) {
96
85
Expr = Node->ASTNode .get <clang::Expr>();
97
- if (isExtractableExpr (Expr)) {
98
- ReferencedDecls = computeReferencedDecls (Expr);
99
- InsertionPoint = computeInsertionPoint ();
100
- if (InsertionPoint)
101
- Extractable = true ;
102
- }
86
+ ReferencedDecls = computeReferencedDecls (Expr);
87
+ InsertionPoint = computeInsertionPoint ();
88
+ if (InsertionPoint)
89
+ Extractable = true ;
103
90
}
104
91
105
92
// checks whether extracting before InsertionPoint will take a
@@ -121,9 +108,9 @@ bool ExtractionContext::exprIsValidOutside(const clang::Stmt *Scope) const {
121
108
// the current Stmt. We ALWAYS insert before a Stmt whose parent is a
122
109
// CompoundStmt
123
110
//
124
-
125
- // FIXME: Extraction from switch and case statements
111
+ // FIXME: Extraction from label, switch and case statements
126
112
// FIXME: Doens't work for FoldExpr
113
+ // FIXME: Ensure extraction from loops doesn't change semantics.
127
114
const clang::Stmt *ExtractionContext::computeInsertionPoint () const {
128
115
// returns true if we can extract before InsertionPoint
129
116
auto CanExtractOutside =
@@ -141,8 +128,7 @@ const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
141
128
return isa<AttributedStmt>(Stmt) || isa<CompoundStmt>(Stmt) ||
142
129
isa<CXXForRangeStmt>(Stmt) || isa<DeclStmt>(Stmt) ||
143
130
isa<DoStmt>(Stmt) || isa<ForStmt>(Stmt) || isa<IfStmt>(Stmt) ||
144
- isa<LabelStmt>(Stmt) || isa<ReturnStmt>(Stmt) ||
145
- isa<WhileStmt>(Stmt);
131
+ isa<ReturnStmt>(Stmt) || isa<WhileStmt>(Stmt);
146
132
}
147
133
if (InsertionPoint->ASTNode .get <VarDecl>())
148
134
return true ;
@@ -209,21 +195,23 @@ class ExtractVariable : public Tweak {
209
195
return " Extract subexpression to variable" ;
210
196
}
211
197
Intent intent () const override { return Refactor; }
198
+ // Compute the extraction context for the Selection
199
+ bool computeExtractionContext (const SelectionTree::Node *N,
200
+ const SourceManager &SM, const ASTContext &Ctx);
212
201
213
202
private:
214
203
// the expression to extract
215
204
std::unique_ptr<ExtractionContext> Target;
216
205
};
217
206
REGISTER_TWEAK (ExtractVariable)
218
207
bool ExtractVariable::prepare (const Selection &Inputs) {
208
+ // we don't trigger on empty selections for now
209
+ if (Inputs.SelectionBegin == Inputs.SelectionEnd )
210
+ return false ;
219
211
const ASTContext &Ctx = Inputs.AST .getASTContext ();
220
212
const SourceManager &SM = Inputs.AST .getSourceManager ();
221
213
const SelectionTree::Node *N = Inputs.ASTSelection .commonAncestor ();
222
- // we don't trigger on empty selections for now
223
- if (!N || Inputs.SelectionBegin == Inputs.SelectionEnd )
224
- return false ;
225
- Target = llvm::make_unique<ExtractionContext>(N, SM, Ctx);
226
- return Target->isExtractable ();
214
+ return computeExtractionContext (N, SM, Ctx);
227
215
}
228
216
229
217
Expected<Tweak::Effect> ExtractVariable::apply (const Selection &Inputs) {
@@ -239,6 +227,75 @@ Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
239
227
return Effect::applyEdit (Result);
240
228
}
241
229
230
+ // Find the CallExpr whose callee is an ancestor of the DeclRef
231
+ const SelectionTree::Node *getCallExpr (const SelectionTree::Node *DeclRef) {
232
+ // we maintain a stack of all exprs encountered while traversing the
233
+ // selectiontree because the callee of the callexpr can be an ancestor of the
234
+ // DeclRef. e.g. Callee can be an ImplicitCastExpr.
235
+ std::vector<const clang::Expr *> ExprStack;
236
+ for (auto *CurNode = DeclRef; CurNode; CurNode = CurNode->Parent ) {
237
+ const Expr *CurExpr = CurNode->ASTNode .get <Expr>();
238
+ if (const CallExpr *CallPar = CurNode->ASTNode .get <CallExpr>()) {
239
+ // check whether the callee of the callexpr is present in Expr stack.
240
+ if (std::find (ExprStack.begin (), ExprStack.end (), CallPar->getCallee ()) !=
241
+ ExprStack.end ())
242
+ return CurNode;
243
+ return nullptr ;
244
+ }
245
+ ExprStack.push_back (CurExpr);
246
+ }
247
+ return nullptr ;
248
+ }
249
+
250
+ // check if Expr can be assigned to a variable i.e. is non-void type
251
+ bool canBeAssigned (const SelectionTree::Node *ExprNode) {
252
+ const clang::Expr *Expr = ExprNode->ASTNode .get <clang::Expr>();
253
+ if (const Type *ExprType = Expr->getType ().getTypePtrOrNull ())
254
+ // FIXME: check if we need to cover any other types
255
+ return !ExprType->isVoidType ();
256
+ return true ;
257
+ }
258
+
259
+ // Find the node that will form our ExtractionContext.
260
+ // We don't want to trigger for assignment expressions and variable/field
261
+ // DeclRefs. For function/member function, we want to extract the entire
262
+ // function call.
263
+ bool ExtractVariable::computeExtractionContext (const SelectionTree::Node *N,
264
+ const SourceManager &SM,
265
+ const ASTContext &Ctx) {
266
+ if (!N)
267
+ return false ;
268
+ const clang::Expr *SelectedExpr = N->ASTNode .get <clang::Expr>();
269
+ const SelectionTree::Node *TargetNode = N;
270
+ if (!SelectedExpr)
271
+ return false ;
272
+ // Extracting Exprs like a = 1 gives dummy = a = 1 which isn't useful.
273
+ if (const BinaryOperator *BinOpExpr =
274
+ dyn_cast_or_null<BinaryOperator>(SelectedExpr)) {
275
+ if (BinOpExpr->getOpcode () == BinaryOperatorKind::BO_Assign)
276
+ return false ;
277
+ }
278
+ // For function and member function DeclRefs, we look for a parent that is a
279
+ // CallExpr
280
+ if (const DeclRefExpr *DeclRef =
281
+ dyn_cast_or_null<DeclRefExpr>(SelectedExpr)) {
282
+ // Extracting just a variable isn't that useful.
283
+ if (!isa<FunctionDecl>(DeclRef->getDecl ()))
284
+ return false ;
285
+ TargetNode = getCallExpr (N);
286
+ }
287
+ if (const MemberExpr *Member = dyn_cast_or_null<MemberExpr>(SelectedExpr)) {
288
+ // Extracting just a field member isn't that useful.
289
+ if (!isa<CXXMethodDecl>(Member->getMemberDecl ()))
290
+ return false ;
291
+ TargetNode = getCallExpr (N);
292
+ }
293
+ if (!TargetNode || !canBeAssigned (TargetNode))
294
+ return false ;
295
+ Target = llvm::make_unique<ExtractionContext>(TargetNode, SM, Ctx);
296
+ return Target->isExtractable ();
297
+ }
298
+
242
299
} // namespace
243
300
} // namespace clangd
244
301
} // namespace clang
0 commit comments