27
27
#include " llvm/ADT/StringRef.h"
28
28
#include " llvm/Support/Casting.h"
29
29
#include " llvm/Support/Error.h"
30
+ #include " llvm/Support/raw_ostream.h"
30
31
31
32
namespace clang {
32
33
namespace clangd {
@@ -39,10 +40,14 @@ class ExtractionContext {
39
40
const clang::Expr *getExpr () const { return Expr; }
40
41
const SelectionTree::Node *getExprNode () const { return ExprNode; }
41
42
bool isExtractable () const { return Extractable; }
43
+ // The half-open range for the expression to be extracted.
44
+ SourceRange getExtractionChars () const ;
42
45
// Generate Replacement for replacing selected expression with given VarName
43
- tooling::Replacement replaceWithVar (llvm::StringRef VarName) const ;
46
+ tooling::Replacement replaceWithVar (SourceRange Chars,
47
+ llvm::StringRef VarName) const ;
44
48
// Generate Replacement for declaring the selected Expr as a new variable
45
- tooling::Replacement insertDeclaration (llvm::StringRef VarName) const ;
49
+ tooling::Replacement insertDeclaration (llvm::StringRef VarName,
50
+ SourceRange InitChars) const ;
46
51
47
52
private:
48
53
bool Extractable = false ;
@@ -152,23 +157,20 @@ const clang::Stmt *ExtractionContext::computeInsertionPoint() const {
152
157
}
153
158
return nullptr ;
154
159
}
160
+
155
161
// returns the replacement for substituting the extraction with VarName
156
162
tooling::Replacement
157
- ExtractionContext::replaceWithVar (llvm::StringRef VarName) const {
158
- const llvm::Optional<SourceRange> ExtractionRng =
159
- toHalfOpenFileRange (SM, Ctx.getLangOpts (), getExpr ()->getSourceRange ());
160
- unsigned ExtractionLength = SM.getFileOffset (ExtractionRng->getEnd ()) -
161
- SM.getFileOffset (ExtractionRng->getBegin ());
162
- return tooling::Replacement (SM, ExtractionRng->getBegin (), ExtractionLength,
163
- VarName);
163
+ ExtractionContext::replaceWithVar (SourceRange Chars,
164
+ llvm::StringRef VarName) const {
165
+ unsigned ExtractionLength =
166
+ SM.getFileOffset (Chars.getEnd ()) - SM.getFileOffset (Chars.getBegin ());
167
+ return tooling::Replacement (SM, Chars.getBegin (), ExtractionLength, VarName);
164
168
}
165
169
// returns the Replacement for declaring a new variable storing the extraction
166
170
tooling::Replacement
167
- ExtractionContext::insertDeclaration (llvm::StringRef VarName) const {
168
- const llvm::Optional<SourceRange> ExtractionRng =
169
- toHalfOpenFileRange (SM, Ctx.getLangOpts (), getExpr ()->getSourceRange ());
170
- assert (ExtractionRng && " ExtractionRng should not be null" );
171
- llvm::StringRef ExtractionCode = toSourceCode (SM, *ExtractionRng);
171
+ ExtractionContext::insertDeclaration (llvm::StringRef VarName,
172
+ SourceRange InitializerChars) const {
173
+ llvm::StringRef ExtractionCode = toSourceCode (SM, InitializerChars);
172
174
const SourceLocation InsertionLoc =
173
175
toHalfOpenFileRange (SM, Ctx.getLangOpts (),
174
176
InsertionPoint->getSourceRange ())
@@ -179,6 +181,144 @@ ExtractionContext::insertDeclaration(llvm::StringRef VarName) const {
179
181
return tooling::Replacement (SM, InsertionLoc, 0 , ExtractedVarDecl);
180
182
}
181
183
184
+ // Helpers for handling "binary subexpressions" like a + [[b + c]] + d.
185
+ //
186
+ // These are special, because the formal AST doesn't match what users expect:
187
+ // - the AST is ((a + b) + c) + d, so the ancestor expression is `a + b + c`.
188
+ // - but extracting `b + c` is reasonable, as + is (mathematically) associative.
189
+ //
190
+ // So we try to support these cases with some restrictions:
191
+ // - the operator must be associative
192
+ // - no mixing of operators is allowed
193
+ // - we don't look inside macro expansions in the subexpressions
194
+ // - we only adjust the extracted range, so references in the unselected parts
195
+ // of the AST expression (e.g. `a`) are still considered referenced for
196
+ // the purposes of calculating the insertion point.
197
+ // FIXME: it would be nice to exclude these references, by micromanaging
198
+ // the computeReferencedDecls() calls around the binary operator tree.
199
+
200
+ // Information extracted about a binary operator encounted in a SelectionTree.
201
+ // It can represent either an overloaded or built-in operator.
202
+ struct ParsedBinaryOperator {
203
+ BinaryOperatorKind Kind;
204
+ SourceLocation ExprLoc;
205
+ llvm::SmallVector<const SelectionTree::Node*, 8 > SelectedOperands;
206
+
207
+ // If N is a binary operator, populate this and return true.
208
+ bool parse (const SelectionTree::Node &N) {
209
+ SelectedOperands.clear ();
210
+
211
+ if (const BinaryOperator *Op =
212
+ llvm::dyn_cast_or_null<BinaryOperator>(N.ASTNode .get <Expr>())) {
213
+ Kind = Op->getOpcode ();
214
+ ExprLoc = Op->getExprLoc ();
215
+ SelectedOperands = N.Children ;
216
+ return true ;
217
+ }
218
+ if (const CXXOperatorCallExpr *Op =
219
+ llvm::dyn_cast_or_null<CXXOperatorCallExpr>(
220
+ N.ASTNode .get <Expr>())) {
221
+ if (!Op->isInfixBinaryOp ())
222
+ return false ;
223
+
224
+ Kind = BinaryOperator::getOverloadedOpcode (Op->getOperator ());
225
+ ExprLoc = Op->getExprLoc ();
226
+ // Not all children are args, there's also the callee (operator).
227
+ for (const auto * Child : N.Children ) {
228
+ const Expr *E = Child->ASTNode .get <Expr>();
229
+ assert (E && " callee and args should be Exprs!" );
230
+ if (E == Op->getArg (0 ) || E == Op->getArg (1 ))
231
+ SelectedOperands.push_back (Child);
232
+ }
233
+ return true ;
234
+ }
235
+ return false ;
236
+ }
237
+
238
+ bool associative () const {
239
+ // Must also be left-associative, or update getBinaryOperatorRange()!
240
+ switch (Kind) {
241
+ case BO_Add:
242
+ case BO_Mul:
243
+ case BO_And:
244
+ case BO_Or:
245
+ case BO_Xor:
246
+ case BO_LAnd:
247
+ case BO_LOr:
248
+ return true ;
249
+ default :
250
+ return false ;
251
+ }
252
+ }
253
+
254
+ bool crossesMacroBoundary (const SourceManager &SM) {
255
+ FileID F = SM.getFileID (ExprLoc);
256
+ for (const SelectionTree::Node *Child : SelectedOperands)
257
+ if (SM.getFileID (Child->ASTNode .get <Expr>()->getExprLoc ()) != F)
258
+ return true ;
259
+ return false ;
260
+ }
261
+ };
262
+
263
+ // If have an associative operator at the top level, then we must find
264
+ // the start point (rightmost in LHS) and end point (leftmost in RHS).
265
+ // We can only descend into subtrees where the operator matches.
266
+ //
267
+ // e.g. for a + [[b + c]] + d
268
+ // +
269
+ // / \
270
+ // N-> + d
271
+ // / \
272
+ // + c <- End
273
+ // / \
274
+ // a b <- Start
275
+ const SourceRange getBinaryOperatorRange (const SelectionTree::Node &N,
276
+ const SourceManager &SM,
277
+ const LangOptions &LangOpts) {
278
+ // If N is not a suitable binary operator, bail out.
279
+ ParsedBinaryOperator Op;
280
+ if (!Op.parse (N.ignoreImplicit ()) || !Op.associative () ||
281
+ Op.crossesMacroBoundary (SM) || Op.SelectedOperands .size () != 2 )
282
+ return SourceRange ();
283
+ BinaryOperatorKind OuterOp = Op.Kind ;
284
+
285
+ // Because the tree we're interested in contains only one operator type, and
286
+ // all eligible operators are left-associative, the shape of the tree is
287
+ // very restricted: it's a linked list along the left edges.
288
+ // This simplifies our implementation.
289
+ const SelectionTree::Node *Start = Op.SelectedOperands .front (); // LHS
290
+ const SelectionTree::Node *End = Op.SelectedOperands .back (); // RHS
291
+ // End is already correct: it can't be an OuterOp (as it's left-associative).
292
+ // Start needs to be pushed down int the subtree to the right spot.
293
+ while (Op.parse (Start->ignoreImplicit ()) && Op.Kind == OuterOp &&
294
+ !Op.crossesMacroBoundary (SM)) {
295
+ assert (!Op.SelectedOperands .empty () && " got only operator on one side!" );
296
+ if (Op.SelectedOperands .size () == 1 ) { // Only Op.RHS selected
297
+ Start = Op.SelectedOperands .back ();
298
+ break ;
299
+ }
300
+ // Op.LHS is (at least partially) selected, so descend into it.
301
+ Start = Op.SelectedOperands .front ();
302
+ }
303
+
304
+ return SourceRange (
305
+ toHalfOpenFileRange (SM, LangOpts, Start->ASTNode .getSourceRange ())
306
+ ->getBegin (),
307
+ toHalfOpenFileRange (SM, LangOpts, End->ASTNode .getSourceRange ())
308
+ ->getEnd ());
309
+ }
310
+
311
+ SourceRange ExtractionContext::getExtractionChars () const {
312
+ // Special case: we're extracting an associative binary subexpression.
313
+ SourceRange BinaryOperatorRange =
314
+ getBinaryOperatorRange (*ExprNode, SM, Ctx.getLangOpts ());
315
+ if (BinaryOperatorRange.isValid ())
316
+ return BinaryOperatorRange;
317
+
318
+ // Usual case: we're extracting the whole expression.
319
+ return *toHalfOpenFileRange (SM, Ctx.getLangOpts (), Expr->getSourceRange ());
320
+ }
321
+
182
322
// / Extracts an expression to the variable dummy
183
323
// / Before:
184
324
// / int x = 5 + 4 * 3;
@@ -218,11 +358,12 @@ Expected<Tweak::Effect> ExtractVariable::apply(const Selection &Inputs) {
218
358
tooling::Replacements Result;
219
359
// FIXME: get variable name from user or suggest based on type
220
360
std::string VarName = " dummy" ;
361
+ SourceRange Range = Target->getExtractionChars ();
221
362
// insert new variable declaration
222
- if (auto Err = Result.add (Target->insertDeclaration (VarName)))
363
+ if (auto Err = Result.add (Target->insertDeclaration (VarName, Range )))
223
364
return std::move (Err);
224
365
// replace expression with variable name
225
- if (auto Err = Result.add (Target->replaceWithVar (VarName)))
366
+ if (auto Err = Result.add (Target->replaceWithVar (Range, VarName)))
226
367
return std::move (Err);
227
368
return Effect::applyEdit (Result);
228
369
}
0 commit comments