blob: 1532c951a26bb3ecfba3c13cf2feb65176d4f3e6 [file] [log] [blame]
Shaurya Guptabf477342019-08-28 19:34:17 +00001//===--- ExtractFunction.cpp -------------------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Extracts statements to a new function and replaces the statements with a
10// call to the new function.
11// Before:
12// void f(int a) {
13// [[if(a < 5)
14// a = 5;]]
15// }
16// After:
17// void extracted(int &a) {
18// if(a < 5)
19// a = 5;
20// }
21// void f(int a) {
22// extracted(a);
23// }
24//
25// - Only extract statements
26// - Extracts from non-templated free functions only.
27// - Parameters are const only if the declaration was const
28// - Always passed by l-value reference
29// - Void return type
30// - Cannot extract declarations that will be needed in the original function
31// after extraction.
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +000032// - Checks for broken control flow (break/continue without loop/switch)
Shaurya Guptabf477342019-08-28 19:34:17 +000033//
34// 1. ExtractFunction is the tweak subclass
35// - Prepare does basic analysis of the selection and is therefore fast.
36// Successful prepare doesn't always mean we can apply the tweak.
37// - Apply does a more detailed analysis and can be slower. In case of
38// failure, we let the user know that we are unable to perform extraction.
39// 2. ExtractionZone store information about the range being extracted and the
40// enclosing function.
41// 3. NewFunction stores properties of the extracted function and provides
42// methods for rendering it.
43// 4. CapturedZoneInfo uses a RecursiveASTVisitor to capture information about
44// the extraction like declarations, existing return statements, etc.
45// 5. getExtractedFunction is responsible for analyzing the CapturedZoneInfo and
46// creating a NewFunction.
47//===----------------------------------------------------------------------===//
48
49#include "AST.h"
Shaurya Guptabf477342019-08-28 19:34:17 +000050#include "Logger.h"
Sam McCall915f9782019-09-04 09:46:06 +000051#include "ParsedAST.h"
Shaurya Guptabf477342019-08-28 19:34:17 +000052#include "Selection.h"
53#include "SourceCode.h"
54#include "refactor/Tweak.h"
55#include "clang/AST/ASTContext.h"
56#include "clang/AST/Decl.h"
57#include "clang/AST/DeclTemplate.h"
58#include "clang/AST/RecursiveASTVisitor.h"
59#include "clang/AST/Stmt.h"
60#include "clang/Basic/LangOptions.h"
61#include "clang/Basic/SourceLocation.h"
62#include "clang/Basic/SourceManager.h"
63#include "clang/Lex/Lexer.h"
64#include "clang/Tooling/Core/Replacement.h"
65#include "clang/Tooling/Refactoring/Extract/SourceExtraction.h"
66#include "llvm/ADT/None.h"
67#include "llvm/ADT/Optional.h"
68#include "llvm/ADT/SmallVector.h"
69#include "llvm/ADT/StringRef.h"
70#include "llvm/ADT/iterator_range.h"
71#include "llvm/Support/Casting.h"
72#include "llvm/Support/Error.h"
73
74namespace clang {
75namespace clangd {
76namespace {
77
78using Node = SelectionTree::Node;
79
80// ExtractionZone is the part of code that is being extracted.
81// EnclosingFunction is the function/method inside which the zone lies.
82// We split the file into 4 parts relative to extraction zone.
83enum class ZoneRelative {
84 Before, // Before Zone and inside EnclosingFunction.
85 Inside, // Inside Zone.
86 After, // After Zone and inside EnclosingFunction.
87 OutsideFunc // Outside EnclosingFunction.
88};
89
90// A RootStmt is a statement that's fully selected including all it's children
91// and it's parent is unselected.
92// Check if a node is a root statement.
93bool isRootStmt(const Node *N) {
94 if (!N->ASTNode.get<Stmt>())
95 return false;
96 // Root statement cannot be partially selected.
97 if (N->Selected == SelectionTree::Partial)
98 return false;
99 // Only DeclStmt can be an unselected RootStmt since VarDecls claim the entire
100 // selection range in selectionTree.
101 if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>())
102 return false;
103 return true;
104}
105
106// Returns the (unselected) parent of all RootStmts given the commonAncestor.
107// We only support extraction of RootStmts since it allows us to extract without
108// having to change the selection range. Also, this means that any scope that
109// begins in selection range, ends in selection range and any scope that begins
110// outside the selection range, ends outside as well.
111const Node *getParentOfRootStmts(const Node *CommonAnc) {
112 if (!CommonAnc)
113 return nullptr;
114 switch (CommonAnc->Selected) {
115 case SelectionTree::Selection::Unselected:
116 // Ensure all Children are RootStmts.
117 return llvm::all_of(CommonAnc->Children, isRootStmt) ? CommonAnc : nullptr;
118 case SelectionTree::Selection::Partial:
119 // Treat Partially selected VarDecl as completely selected since
120 // SelectionTree doesn't always select VarDecls correctly.
121 // FIXME: Remove this after D66872 is upstream)
122 if (!CommonAnc->ASTNode.get<VarDecl>())
123 return nullptr;
124 LLVM_FALLTHROUGH;
125 case SelectionTree::Selection::Complete:
126 // If the Common Ancestor is completely selected, then it's a root statement
127 // and its parent will be unselected.
128 const Node *Parent = CommonAnc->Parent;
129 // If parent is a DeclStmt, even though it's unselected, we consider it a
130 // root statement and return its parent. This is done because the VarDecls
131 // claim the entire selection range of the Declaration and DeclStmt is
132 // always unselected.
133 return Parent->ASTNode.get<DeclStmt>() ? Parent->Parent : Parent;
134 }
Simon Pilgrim46b346e2019-08-29 12:37:02 +0000135 llvm_unreachable("Unhandled SelectionTree::Selection enum");
Shaurya Guptabf477342019-08-28 19:34:17 +0000136}
137
138// The ExtractionZone class forms a view of the code wrt Zone.
139struct ExtractionZone {
140 // Parent of RootStatements being extracted.
141 const Node *Parent = nullptr;
142 // The half-open file range of the code being extracted.
143 SourceRange ZoneRange;
144 // The function inside which our zone resides.
145 const FunctionDecl *EnclosingFunction = nullptr;
146 // The half-open file range of the enclosing function.
147 SourceRange EnclosingFuncRange;
148 SourceLocation getInsertionPoint() const {
149 return EnclosingFuncRange.getBegin();
150 }
151 bool isRootStmt(const Stmt *S) const;
152 // The last root statement is important to decide where we need to insert a
153 // semicolon after the extraction.
154 const Node *getLastRootStmt() const { return Parent->Children.back(); }
155 void generateRootStmts();
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000156
Shaurya Guptabf477342019-08-28 19:34:17 +0000157private:
158 llvm::DenseSet<const Stmt *> RootStmts;
159};
160
161bool ExtractionZone::isRootStmt(const Stmt *S) const {
162 return RootStmts.find(S) != RootStmts.end();
163}
164
165// Generate RootStmts set
166void ExtractionZone::generateRootStmts() {
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000167 for (const Node *Child : Parent->Children)
Shaurya Guptabf477342019-08-28 19:34:17 +0000168 RootStmts.insert(Child->ASTNode.get<Stmt>());
169}
170
171// Finds the function in which the zone lies.
172const FunctionDecl *findEnclosingFunction(const Node *CommonAnc) {
173 // Walk up the SelectionTree until we find a function Decl
174 for (const Node *CurNode = CommonAnc; CurNode; CurNode = CurNode->Parent) {
175 // Don't extract from lambdas
176 if (CurNode->ASTNode.get<LambdaExpr>())
177 return nullptr;
178 if (const FunctionDecl *Func = CurNode->ASTNode.get<FunctionDecl>()) {
179 // FIXME: Support extraction from methods.
180 if (isa<CXXMethodDecl>(Func))
181 return nullptr;
182 // FIXME: Support extraction from templated functions.
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000183 if (Func->isTemplated())
Shaurya Guptabf477342019-08-28 19:34:17 +0000184 return nullptr;
185 return Func;
186 }
187 }
188 return nullptr;
189}
190
191// Zone Range is the union of SourceRanges of all child Nodes in Parent since
192// all child Nodes are RootStmts
193llvm::Optional<SourceRange> findZoneRange(const Node *Parent,
194 const SourceManager &SM,
195 const LangOptions &LangOpts) {
196 SourceRange SR;
197 if (auto BeginFileRange = toHalfOpenFileRange(
198 SM, LangOpts, Parent->Children.front()->ASTNode.getSourceRange()))
199 SR.setBegin(BeginFileRange->getBegin());
200 else
201 return llvm::None;
202 if (auto EndFileRange = toHalfOpenFileRange(
203 SM, LangOpts, Parent->Children.back()->ASTNode.getSourceRange()))
204 SR.setEnd(EndFileRange->getEnd());
205 else
206 return llvm::None;
207 return SR;
208}
209
210// Compute the range spanned by the enclosing function.
211// FIXME: check if EnclosingFunction has any attributes as the AST doesn't
212// always store the source range of the attributes and thus we end up extracting
213// between the attributes and the EnclosingFunction.
214llvm::Optional<SourceRange>
215computeEnclosingFuncRange(const FunctionDecl *EnclosingFunction,
216 const SourceManager &SM,
217 const LangOptions &LangOpts) {
218 return toHalfOpenFileRange(SM, LangOpts, EnclosingFunction->getSourceRange());
219}
220
221// FIXME: Check we're not extracting from the initializer/condition of a control
222// flow structure.
223// FIXME: Check that we don't extract the compound statement of the
224// enclosingFunction.
225llvm::Optional<ExtractionZone> findExtractionZone(const Node *CommonAnc,
226 const SourceManager &SM,
227 const LangOptions &LangOpts) {
228 ExtractionZone ExtZone;
229 ExtZone.Parent = getParentOfRootStmts(CommonAnc);
230 if (!ExtZone.Parent || ExtZone.Parent->Children.empty())
231 return llvm::None;
232 // Don't extract expressions.
233 // FIXME: We should extract expressions that are "statements" i.e. not
234 // subexpressions
235 if (ExtZone.Parent->Children.size() == 1 &&
236 ExtZone.getLastRootStmt()->ASTNode.get<Expr>())
237 return llvm::None;
238 ExtZone.EnclosingFunction = findEnclosingFunction(ExtZone.Parent);
239 if (!ExtZone.EnclosingFunction)
240 return llvm::None;
241 if (auto FuncRange =
242 computeEnclosingFuncRange(ExtZone.EnclosingFunction, SM, LangOpts))
243 ExtZone.EnclosingFuncRange = *FuncRange;
244 if (auto ZoneRange = findZoneRange(ExtZone.Parent, SM, LangOpts))
245 ExtZone.ZoneRange = *ZoneRange;
246 if (ExtZone.EnclosingFuncRange.isInvalid() || ExtZone.ZoneRange.isInvalid())
247 return llvm::None;
248 ExtZone.generateRootStmts();
249 return ExtZone;
250}
251
252// Stores information about the extracted function and provides methods for
253// rendering it.
254struct NewFunction {
255 struct Parameter {
256 std::string Name;
257 QualType TypeInfo;
258 bool PassByReference;
259 unsigned OrderPriority; // Lower value parameters are preferred first.
260 std::string render(const DeclContext *Context) const;
261 bool operator<(const Parameter &Other) const {
262 return OrderPriority < Other.OrderPriority;
263 }
264 };
265 std::string Name = "extracted";
266 std::string ReturnType;
267 std::vector<Parameter> Parameters;
268 SourceRange BodyRange;
269 SourceLocation InsertionPoint;
270 const DeclContext *EnclosingFuncContext;
271 // Decides whether the extracted function body and the function call need a
272 // semicolon after extraction.
273 tooling::ExtractionSemicolonPolicy SemicolonPolicy;
274 NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy)
275 : SemicolonPolicy(SemicolonPolicy) {}
276 // Render the call for this function.
277 std::string renderCall() const;
278 // Render the definition for this function.
279 std::string renderDefinition(const SourceManager &SM) const;
280
281private:
282 std::string renderParametersForDefinition() const;
283 std::string renderParametersForCall() const;
284 // Generate the function body.
285 std::string getFuncBody(const SourceManager &SM) const;
286};
287
288std::string NewFunction::renderParametersForDefinition() const {
289 std::string Result;
290 bool NeedCommaBefore = false;
291 for (const Parameter &P : Parameters) {
292 if (NeedCommaBefore)
293 Result += ", ";
294 NeedCommaBefore = true;
295 Result += P.render(EnclosingFuncContext);
296 }
297 return Result;
298}
299
300std::string NewFunction::renderParametersForCall() const {
301 std::string Result;
302 bool NeedCommaBefore = false;
303 for (const Parameter &P : Parameters) {
304 if (NeedCommaBefore)
305 Result += ", ";
306 NeedCommaBefore = true;
307 Result += P.Name;
308 }
309 return Result;
310}
311
312std::string NewFunction::renderCall() const {
313 return Name + "(" + renderParametersForCall() + ")" +
314 (SemicolonPolicy.isNeededInOriginalFunction() ? ";" : "");
315}
316
317std::string NewFunction::renderDefinition(const SourceManager &SM) const {
318 return ReturnType + " " + Name + "(" + renderParametersForDefinition() + ")" +
319 " {\n" + getFuncBody(SM) + "\n}\n";
320}
321
322std::string NewFunction::getFuncBody(const SourceManager &SM) const {
323 // FIXME: Generate tooling::Replacements instead of std::string to
324 // - hoist decls
325 // - add return statement
326 // - Add semicolon
327 return toSourceCode(SM, BodyRange).str() +
328 (SemicolonPolicy.isNeededInExtractedFunction() ? ";" : "");
329}
330
331std::string NewFunction::Parameter::render(const DeclContext *Context) const {
332 return printType(TypeInfo, *Context) + (PassByReference ? " &" : " ") + Name;
333}
334
335// Stores captured information about Extraction Zone.
336struct CapturedZoneInfo {
337 struct DeclInformation {
338 const Decl *TheDecl;
339 ZoneRelative DeclaredIn;
340 // index of the declaration or first reference.
341 unsigned DeclIndex;
342 bool IsReferencedInZone = false;
343 bool IsReferencedInPostZone = false;
344 // FIXME: Capture mutation information
345 DeclInformation(const Decl *TheDecl, ZoneRelative DeclaredIn,
346 unsigned DeclIndex)
347 : TheDecl(TheDecl), DeclaredIn(DeclaredIn), DeclIndex(DeclIndex){};
348 // Marks the occurence of a reference for this declaration
349 void markOccurence(ZoneRelative ReferenceLoc);
350 };
351 // Maps Decls to their DeclInfo
352 llvm::DenseMap<const Decl *, DeclInformation> DeclInfoMap;
353 // True if there is a return statement in zone.
354 bool HasReturnStmt = false;
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000355 // Control flow is broken if we are extracting a break/continue without a
356 // corresponding parent loop/switch
357 bool BrokenControlFlow = false;
Shaurya Guptabf477342019-08-28 19:34:17 +0000358 // FIXME: capture TypeAliasDecl and UsingDirectiveDecl
359 // FIXME: Capture type information as well.
360 DeclInformation *createDeclInfo(const Decl *D, ZoneRelative RelativeLoc);
361 DeclInformation *getDeclInfoFor(const Decl *D);
362};
363
364CapturedZoneInfo::DeclInformation *
365CapturedZoneInfo::createDeclInfo(const Decl *D, ZoneRelative RelativeLoc) {
366 // The new Decl's index is the size of the map so far.
367 auto InsertionResult = DeclInfoMap.insert(
368 {D, DeclInformation(D, RelativeLoc, DeclInfoMap.size())});
369 // Return the newly created DeclInfo
370 return &InsertionResult.first->second;
371}
372
373CapturedZoneInfo::DeclInformation *
374CapturedZoneInfo::getDeclInfoFor(const Decl *D) {
375 // If the Decl doesn't exist, we
376 auto Iter = DeclInfoMap.find(D);
377 if (Iter == DeclInfoMap.end())
378 return nullptr;
379 return &Iter->second;
380}
381
382void CapturedZoneInfo::DeclInformation::markOccurence(
383 ZoneRelative ReferenceLoc) {
384 switch (ReferenceLoc) {
385 case ZoneRelative::Inside:
386 IsReferencedInZone = true;
387 break;
388 case ZoneRelative::After:
389 IsReferencedInPostZone = true;
390 break;
391 default:
392 break;
393 }
394}
395
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000396bool isLoop(const Stmt *S) {
397 return isa<ForStmt>(S) || isa<DoStmt>(S) || isa<WhileStmt>(S) ||
398 isa<CXXForRangeStmt>(S);
399}
400
Shaurya Guptabf477342019-08-28 19:34:17 +0000401// Captures information from Extraction Zone
402CapturedZoneInfo captureZoneInfo(const ExtractionZone &ExtZone) {
403 // We use the ASTVisitor instead of using the selection tree since we need to
404 // find references in the PostZone as well.
405 // FIXME: Check which statements we don't allow to extract.
406 class ExtractionZoneVisitor
407 : public clang::RecursiveASTVisitor<ExtractionZoneVisitor> {
408 public:
409 ExtractionZoneVisitor(const ExtractionZone &ExtZone) : ExtZone(ExtZone) {
410 TraverseDecl(const_cast<FunctionDecl *>(ExtZone.EnclosingFunction));
411 }
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000412
Shaurya Guptabf477342019-08-28 19:34:17 +0000413 bool TraverseStmt(Stmt *S) {
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000414 if (!S)
415 return true;
Shaurya Guptabf477342019-08-28 19:34:17 +0000416 bool IsRootStmt = ExtZone.isRootStmt(const_cast<const Stmt *>(S));
417 // If we are starting traversal of a RootStmt, we are somewhere inside
418 // ExtractionZone
419 if (IsRootStmt)
420 CurrentLocation = ZoneRelative::Inside;
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000421 addToLoopSwitchCounters(S, 1);
Shaurya Guptabf477342019-08-28 19:34:17 +0000422 // Traverse using base class's TraverseStmt
423 RecursiveASTVisitor::TraverseStmt(S);
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000424 addToLoopSwitchCounters(S, -1);
Shaurya Guptabf477342019-08-28 19:34:17 +0000425 // We set the current location as after since next stmt will either be a
426 // RootStmt (handled at the beginning) or after extractionZone
427 if (IsRootStmt)
428 CurrentLocation = ZoneRelative::After;
429 return true;
430 }
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000431
432 // Add Increment to CurNumberOf{Loops,Switch} if statement is
433 // {Loop,Switch} and inside Extraction Zone.
434 void addToLoopSwitchCounters(Stmt *S, int Increment) {
435 if (CurrentLocation != ZoneRelative::Inside)
436 return;
437 if (isLoop(S))
438 CurNumberOfNestedLoops += Increment;
439 else if (isa<SwitchStmt>(S))
440 CurNumberOfSwitch += Increment;
441 }
442
443 // Decrement CurNumberOf{NestedLoops,Switch} if statement is {Loop,Switch}
444 // and inside Extraction Zone.
445 void decrementLoopSwitchCounters(Stmt *S) {
446 if (CurrentLocation != ZoneRelative::Inside)
447 return;
448 if (isLoop(S))
449 CurNumberOfNestedLoops--;
450 else if (isa<SwitchStmt>(S))
451 CurNumberOfSwitch--;
452 }
453
Shaurya Guptabf477342019-08-28 19:34:17 +0000454 bool VisitDecl(Decl *D) {
455 Info.createDeclInfo(D, CurrentLocation);
456 return true;
457 }
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000458
Shaurya Guptabf477342019-08-28 19:34:17 +0000459 bool VisitDeclRefExpr(DeclRefExpr *DRE) {
460 // Find the corresponding Decl and mark it's occurence.
461 const Decl *D = DRE->getDecl();
462 auto *DeclInfo = Info.getDeclInfoFor(D);
463 // If no Decl was found, the Decl must be outside the enclosingFunc.
464 if (!DeclInfo)
465 DeclInfo = Info.createDeclInfo(D, ZoneRelative::OutsideFunc);
466 DeclInfo->markOccurence(CurrentLocation);
467 // FIXME: check if reference mutates the Decl being referred.
468 return true;
469 }
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000470
Shaurya Guptabf477342019-08-28 19:34:17 +0000471 bool VisitReturnStmt(ReturnStmt *Return) {
472 if (CurrentLocation == ZoneRelative::Inside)
473 Info.HasReturnStmt = true;
474 return true;
475 }
476
Shaurya Guptabf477342019-08-28 19:34:17 +0000477 bool VisitBreakStmt(BreakStmt *Break) {
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000478 // Control flow is broken if break statement is selected without any
479 // parent loop or switch statement.
480 if (CurrentLocation == ZoneRelative::Inside &&
481 !(CurNumberOfNestedLoops || CurNumberOfSwitch))
482 Info.BrokenControlFlow = true;
Shaurya Guptabf477342019-08-28 19:34:17 +0000483 return true;
484 }
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000485
Shaurya Guptabf477342019-08-28 19:34:17 +0000486 bool VisitContinueStmt(ContinueStmt *Continue) {
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000487 // Control flow is broken if Continue statement is selected without any
488 // parent loop
489 if (CurrentLocation == ZoneRelative::Inside && !CurNumberOfNestedLoops)
490 Info.BrokenControlFlow = true;
Shaurya Guptabf477342019-08-28 19:34:17 +0000491 return true;
492 }
493 CapturedZoneInfo Info;
494 const ExtractionZone &ExtZone;
495 ZoneRelative CurrentLocation = ZoneRelative::Before;
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000496 // Number of {loop,switch} statements that are currently in the traversal
497 // stack inside Extraction Zone. Used to check for broken control flow.
498 unsigned CurNumberOfNestedLoops = 0;
499 unsigned CurNumberOfSwitch = 0;
Shaurya Guptabf477342019-08-28 19:34:17 +0000500 };
501 ExtractionZoneVisitor Visitor(ExtZone);
502 return std::move(Visitor.Info);
503}
504
505// Adds parameters to ExtractedFunc.
506// Returns true if able to find the parameters successfully and no hoisting
507// needed.
Shaurya Gupta6a801452019-08-29 15:11:59 +0000508// FIXME: Check if the declaration has a local/anonymous type
Shaurya Guptabf477342019-08-28 19:34:17 +0000509bool createParameters(NewFunction &ExtractedFunc,
510 const CapturedZoneInfo &CapturedInfo) {
511 for (const auto &KeyVal : CapturedInfo.DeclInfoMap) {
512 const auto &DeclInfo = KeyVal.second;
513 // If a Decl was Declared in zone and referenced in post zone, it
514 // needs to be hoisted (we bail out in that case).
515 // FIXME: Support Decl Hoisting.
516 if (DeclInfo.DeclaredIn == ZoneRelative::Inside &&
517 DeclInfo.IsReferencedInPostZone)
518 return false;
519 if (!DeclInfo.IsReferencedInZone)
520 continue; // no need to pass as parameter, not referenced
521 if (DeclInfo.DeclaredIn == ZoneRelative::Inside ||
522 DeclInfo.DeclaredIn == ZoneRelative::OutsideFunc)
523 continue; // no need to pass as parameter, still accessible.
524 // Parameter specific checks.
525 const ValueDecl *VD = dyn_cast_or_null<ValueDecl>(DeclInfo.TheDecl);
526 // Can't parameterise if the Decl isn't a ValueDecl or is a FunctionDecl
527 // (this includes the case of recursive call to EnclosingFunc in Zone).
528 if (!VD || isa<FunctionDecl>(DeclInfo.TheDecl))
529 return false;
530 // Parameter qualifiers are same as the Decl's qualifiers.
531 QualType TypeInfo = VD->getType().getNonReferenceType();
532 // FIXME: Need better qualifier checks: check mutated status for
533 // Decl(e.g. was it assigned, passed as nonconst argument, etc)
534 // FIXME: check if parameter will be a non l-value reference.
535 // FIXME: We don't want to always pass variables of types like int,
536 // pointers, etc by reference.
537 bool IsPassedByReference = true;
538 // We use the index of declaration as the ordering priority for parameters.
539 ExtractedFunc.Parameters.push_back(
540 {VD->getName(), TypeInfo, IsPassedByReference, DeclInfo.DeclIndex});
541 }
542 llvm::sort(ExtractedFunc.Parameters);
543 return true;
544}
545
546// Clangd uses open ranges while ExtractionSemicolonPolicy (in Clang Tooling)
547// uses closed ranges. Generates the semicolon policy for the extraction and
548// extends the ZoneRange if necessary.
549tooling::ExtractionSemicolonPolicy
550getSemicolonPolicy(ExtractionZone &ExtZone, const SourceManager &SM,
551 const LangOptions &LangOpts) {
552 // Get closed ZoneRange.
553 SourceRange FuncBodyRange = {ExtZone.ZoneRange.getBegin(),
554 ExtZone.ZoneRange.getEnd().getLocWithOffset(-1)};
555 auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute(
556 ExtZone.getLastRootStmt()->ASTNode.get<Stmt>(), FuncBodyRange, SM,
557 LangOpts);
558 // Update ZoneRange.
559 ExtZone.ZoneRange.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1));
560 return SemicolonPolicy;
561}
562
563// Generate return type for ExtractedFunc. Return false if unable to do so.
564bool generateReturnProperties(NewFunction &ExtractedFunc,
565 const CapturedZoneInfo &CapturedInfo) {
566
567 // FIXME: Use Existing Return statements (if present)
568 // FIXME: Generate new return statement if needed.
569 if (CapturedInfo.HasReturnStmt)
570 return false;
571 ExtractedFunc.ReturnType = "void";
572 return true;
573}
574
575// FIXME: add support for adding other function return types besides void.
576// FIXME: assign the value returned by non void extracted function.
577llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone,
578 const SourceManager &SM,
579 const LangOptions &LangOpts) {
580 CapturedZoneInfo CapturedInfo = captureZoneInfo(ExtZone);
581 // Bail out if any break of continue exists
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000582 if (CapturedInfo.BrokenControlFlow)
Shaurya Guptabf477342019-08-28 19:34:17 +0000583 return llvm::createStringError(llvm::inconvertibleErrorCode(),
Shaurya Gupta3b08a61f2019-08-30 09:57:56 +0000584 +"Cannot extract break/continue without "
585 "corresponding loop/switch statement.");
Shaurya Guptabf477342019-08-28 19:34:17 +0000586 NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts));
587 ExtractedFunc.BodyRange = ExtZone.ZoneRange;
588 ExtractedFunc.InsertionPoint = ExtZone.getInsertionPoint();
589 ExtractedFunc.EnclosingFuncContext =
590 ExtZone.EnclosingFunction->getDeclContext();
591 if (!createParameters(ExtractedFunc, CapturedInfo) ||
592 !generateReturnProperties(ExtractedFunc, CapturedInfo))
593 return llvm::createStringError(llvm::inconvertibleErrorCode(),
594 +"Too complex to extract.");
595 return ExtractedFunc;
596}
597
598class ExtractFunction : public Tweak {
599public:
600 const char *id() const override final;
601 bool prepare(const Selection &Inputs) override;
602 Expected<Effect> apply(const Selection &Inputs) override;
603 std::string title() const override { return "Extract to function"; }
604 Intent intent() const override { return Refactor; }
605
606private:
607 ExtractionZone ExtZone;
608};
609
610REGISTER_TWEAK(ExtractFunction)
611tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc,
612 const SourceManager &SM,
613 const LangOptions &LangOpts) {
614 std::string FuncCall = ExtractedFunc.renderCall();
615 return tooling::Replacement(
616 SM, CharSourceRange(ExtractedFunc.BodyRange, false), FuncCall, LangOpts);
617}
618
619tooling::Replacement createFunctionDefinition(const NewFunction &ExtractedFunc,
620 const SourceManager &SM) {
621 std::string FunctionDef = ExtractedFunc.renderDefinition(SM);
622 return tooling::Replacement(SM, ExtractedFunc.InsertionPoint, 0, FunctionDef);
623}
624
625bool ExtractFunction::prepare(const Selection &Inputs) {
626 const Node *CommonAnc = Inputs.ASTSelection.commonAncestor();
627 const SourceManager &SM = Inputs.AST.getSourceManager();
628 const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts();
629 if (auto MaybeExtZone = findExtractionZone(CommonAnc, SM, LangOpts)) {
630 ExtZone = std::move(*MaybeExtZone);
631 return true;
632 }
633 return false;
634}
635
636Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {
637 const SourceManager &SM = Inputs.AST.getSourceManager();
638 const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts();
639 auto ExtractedFunc = getExtractedFunction(ExtZone, SM, LangOpts);
640 // FIXME: Add more types of errors.
641 if (!ExtractedFunc)
642 return ExtractedFunc.takeError();
643 tooling::Replacements Result;
644 if (auto Err = Result.add(createFunctionDefinition(*ExtractedFunc, SM)))
645 return std::move(Err);
646 if (auto Err = Result.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts)))
647 return std::move(Err);
Kadir Cetinkaya5b270932019-09-09 12:28:44 +0000648 return Effect::mainFileEdit(SM, std::move(Result));
Shaurya Guptabf477342019-08-28 19:34:17 +0000649}
650
651} // namespace
652} // namespace clangd
653} // namespace clang