1 //===--- 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. 32 // - Checks for broken control flow (break/continue without loop/switch) 33 // 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" 50 #include "Logger.h" 51 #include "ParsedAST.h" 52 #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 74 namespace clang { 75 namespace clangd { 76 namespace { 77 78 using 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. 83 enum 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. 93 bool 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 // Returns null if: 108 // 1. any node is partially selected 109 // 2. If all completely selected nodes don't have the same common parent 110 // 3. Any child of Parent isn't a RootStmt. 111 // Returns null if any child is not a RootStmt. 112 // We only support extraction of RootStmts since it allows us to extract without 113 // having to change the selection range. Also, this means that any scope that 114 // begins in selection range, ends in selection range and any scope that begins 115 // outside the selection range, ends outside as well. 116 const Node *getParentOfRootStmts(const Node *CommonAnc) { 117 if (!CommonAnc) 118 return nullptr; 119 const Node *Parent = nullptr; 120 switch (CommonAnc->Selected) { 121 case SelectionTree::Selection::Unselected: 122 // Typicaly a block, with the { and } unselected, could also be ForStmt etc 123 // Ensure all Children are RootStmts. 124 Parent = CommonAnc; 125 break; 126 case SelectionTree::Selection::Partial: 127 // Only a fully-selected single statement can be selected. 128 return nullptr; 129 case SelectionTree::Selection::Complete: 130 // If the Common Ancestor is completely selected, then it's a root statement 131 // and its parent will be unselected. 132 Parent = CommonAnc->Parent; 133 // If parent is a DeclStmt, even though it's unselected, we consider it a 134 // root statement and return its parent. This is done because the VarDecls 135 // claim the entire selection range of the Declaration and DeclStmt is 136 // always unselected. 137 if (Parent->ASTNode.get<DeclStmt>()) 138 Parent = Parent->Parent; 139 break; 140 } 141 // Ensure all Children are RootStmts. 142 return llvm::all_of(Parent->Children, isRootStmt) ? Parent : nullptr; 143 } 144 145 // The ExtractionZone class forms a view of the code wrt Zone. 146 struct ExtractionZone { 147 // Parent of RootStatements being extracted. 148 const Node *Parent = nullptr; 149 // The half-open file range of the code being extracted. 150 SourceRange ZoneRange; 151 // The function inside which our zone resides. 152 const FunctionDecl *EnclosingFunction = nullptr; 153 // The half-open file range of the enclosing function. 154 SourceRange EnclosingFuncRange; 155 SourceLocation getInsertionPoint() const { 156 return EnclosingFuncRange.getBegin(); 157 } 158 bool isRootStmt(const Stmt *S) const; 159 // The last root statement is important to decide where we need to insert a 160 // semicolon after the extraction. 161 const Node *getLastRootStmt() const { return Parent->Children.back(); } 162 void generateRootStmts(); 163 164 private: 165 llvm::DenseSet<const Stmt *> RootStmts; 166 }; 167 168 bool ExtractionZone::isRootStmt(const Stmt *S) const { 169 return RootStmts.find(S) != RootStmts.end(); 170 } 171 172 // Generate RootStmts set 173 void ExtractionZone::generateRootStmts() { 174 for (const Node *Child : Parent->Children) 175 RootStmts.insert(Child->ASTNode.get<Stmt>()); 176 } 177 178 // Finds the function in which the zone lies. 179 const FunctionDecl *findEnclosingFunction(const Node *CommonAnc) { 180 // Walk up the SelectionTree until we find a function Decl 181 for (const Node *CurNode = CommonAnc; CurNode; CurNode = CurNode->Parent) { 182 // Don't extract from lambdas 183 if (CurNode->ASTNode.get<LambdaExpr>()) 184 return nullptr; 185 if (const FunctionDecl *Func = CurNode->ASTNode.get<FunctionDecl>()) { 186 // FIXME: Support extraction from methods. 187 if (isa<CXXMethodDecl>(Func)) 188 return nullptr; 189 // FIXME: Support extraction from templated functions. 190 if (Func->isTemplated()) 191 return nullptr; 192 return Func; 193 } 194 } 195 return nullptr; 196 } 197 198 // Zone Range is the union of SourceRanges of all child Nodes in Parent since 199 // all child Nodes are RootStmts 200 llvm::Optional<SourceRange> findZoneRange(const Node *Parent, 201 const SourceManager &SM, 202 const LangOptions &LangOpts) { 203 SourceRange SR; 204 if (auto BeginFileRange = toHalfOpenFileRange( 205 SM, LangOpts, Parent->Children.front()->ASTNode.getSourceRange())) 206 SR.setBegin(BeginFileRange->getBegin()); 207 else 208 return llvm::None; 209 if (auto EndFileRange = toHalfOpenFileRange( 210 SM, LangOpts, Parent->Children.back()->ASTNode.getSourceRange())) 211 SR.setEnd(EndFileRange->getEnd()); 212 else 213 return llvm::None; 214 return SR; 215 } 216 217 // Compute the range spanned by the enclosing function. 218 // FIXME: check if EnclosingFunction has any attributes as the AST doesn't 219 // always store the source range of the attributes and thus we end up extracting 220 // between the attributes and the EnclosingFunction. 221 llvm::Optional<SourceRange> 222 computeEnclosingFuncRange(const FunctionDecl *EnclosingFunction, 223 const SourceManager &SM, 224 const LangOptions &LangOpts) { 225 return toHalfOpenFileRange(SM, LangOpts, EnclosingFunction->getSourceRange()); 226 } 227 228 // returns true if Child can be a single RootStmt being extracted from 229 // EnclosingFunc. 230 bool validSingleChild(const Node *Child, const FunctionDecl *EnclosingFunc) { 231 // Don't extract expressions. 232 // FIXME: We should extract expressions that are "statements" i.e. not 233 // subexpressions 234 if (Child->ASTNode.get<Expr>()) 235 return false; 236 // Extracting the body of EnclosingFunc would remove it's definition. 237 assert(EnclosingFunc->hasBody() && 238 "We should always be extracting from a function body."); 239 if (Child->ASTNode.get<Stmt>() == EnclosingFunc->getBody()) 240 return false; 241 return true; 242 } 243 244 // FIXME: Check we're not extracting from the initializer/condition of a control 245 // flow structure. 246 llvm::Optional<ExtractionZone> findExtractionZone(const Node *CommonAnc, 247 const SourceManager &SM, 248 const LangOptions &LangOpts) { 249 ExtractionZone ExtZone; 250 ExtZone.Parent = getParentOfRootStmts(CommonAnc); 251 if (!ExtZone.Parent || ExtZone.Parent->Children.empty()) 252 return llvm::None; 253 ExtZone.EnclosingFunction = findEnclosingFunction(ExtZone.Parent); 254 if (!ExtZone.EnclosingFunction) 255 return llvm::None; 256 // When there is a single RootStmt, we must check if it's valid for 257 // extraction. 258 if (ExtZone.Parent->Children.size() == 1 && 259 !validSingleChild(ExtZone.getLastRootStmt(), ExtZone.EnclosingFunction)) 260 return llvm::None; 261 if (auto FuncRange = 262 computeEnclosingFuncRange(ExtZone.EnclosingFunction, SM, LangOpts)) 263 ExtZone.EnclosingFuncRange = *FuncRange; 264 if (auto ZoneRange = findZoneRange(ExtZone.Parent, SM, LangOpts)) 265 ExtZone.ZoneRange = *ZoneRange; 266 if (ExtZone.EnclosingFuncRange.isInvalid() || ExtZone.ZoneRange.isInvalid()) 267 return llvm::None; 268 ExtZone.generateRootStmts(); 269 return ExtZone; 270 } 271 272 // Stores information about the extracted function and provides methods for 273 // rendering it. 274 struct NewFunction { 275 struct Parameter { 276 std::string Name; 277 QualType TypeInfo; 278 bool PassByReference; 279 unsigned OrderPriority; // Lower value parameters are preferred first. 280 std::string render(const DeclContext *Context) const; 281 bool operator<(const Parameter &Other) const { 282 return OrderPriority < Other.OrderPriority; 283 } 284 }; 285 std::string Name = "extracted"; 286 std::string ReturnType; 287 std::vector<Parameter> Parameters; 288 SourceRange BodyRange; 289 SourceLocation InsertionPoint; 290 const DeclContext *EnclosingFuncContext; 291 // Decides whether the extracted function body and the function call need a 292 // semicolon after extraction. 293 tooling::ExtractionSemicolonPolicy SemicolonPolicy; 294 NewFunction(tooling::ExtractionSemicolonPolicy SemicolonPolicy) 295 : SemicolonPolicy(SemicolonPolicy) {} 296 // Render the call for this function. 297 std::string renderCall() const; 298 // Render the definition for this function. 299 std::string renderDefinition(const SourceManager &SM) const; 300 301 private: 302 std::string renderParametersForDefinition() const; 303 std::string renderParametersForCall() const; 304 // Generate the function body. 305 std::string getFuncBody(const SourceManager &SM) const; 306 }; 307 308 std::string NewFunction::renderParametersForDefinition() const { 309 std::string Result; 310 bool NeedCommaBefore = false; 311 for (const Parameter &P : Parameters) { 312 if (NeedCommaBefore) 313 Result += ", "; 314 NeedCommaBefore = true; 315 Result += P.render(EnclosingFuncContext); 316 } 317 return Result; 318 } 319 320 std::string NewFunction::renderParametersForCall() const { 321 std::string Result; 322 bool NeedCommaBefore = false; 323 for (const Parameter &P : Parameters) { 324 if (NeedCommaBefore) 325 Result += ", "; 326 NeedCommaBefore = true; 327 Result += P.Name; 328 } 329 return Result; 330 } 331 332 std::string NewFunction::renderCall() const { 333 return Name + "(" + renderParametersForCall() + ")" + 334 (SemicolonPolicy.isNeededInOriginalFunction() ? ";" : ""); 335 } 336 337 std::string NewFunction::renderDefinition(const SourceManager &SM) const { 338 return ReturnType + " " + Name + "(" + renderParametersForDefinition() + ")" + 339 " {\n" + getFuncBody(SM) + "\n}\n"; 340 } 341 342 std::string NewFunction::getFuncBody(const SourceManager &SM) const { 343 // FIXME: Generate tooling::Replacements instead of std::string to 344 // - hoist decls 345 // - add return statement 346 // - Add semicolon 347 return toSourceCode(SM, BodyRange).str() + 348 (SemicolonPolicy.isNeededInExtractedFunction() ? ";" : ""); 349 } 350 351 std::string NewFunction::Parameter::render(const DeclContext *Context) const { 352 return printType(TypeInfo, *Context) + (PassByReference ? " &" : " ") + Name; 353 } 354 355 // Stores captured information about Extraction Zone. 356 struct CapturedZoneInfo { 357 struct DeclInformation { 358 const Decl *TheDecl; 359 ZoneRelative DeclaredIn; 360 // index of the declaration or first reference. 361 unsigned DeclIndex; 362 bool IsReferencedInZone = false; 363 bool IsReferencedInPostZone = false; 364 // FIXME: Capture mutation information 365 DeclInformation(const Decl *TheDecl, ZoneRelative DeclaredIn, 366 unsigned DeclIndex) 367 : TheDecl(TheDecl), DeclaredIn(DeclaredIn), DeclIndex(DeclIndex){}; 368 // Marks the occurence of a reference for this declaration 369 void markOccurence(ZoneRelative ReferenceLoc); 370 }; 371 // Maps Decls to their DeclInfo 372 llvm::DenseMap<const Decl *, DeclInformation> DeclInfoMap; 373 // True if there is a return statement in zone. 374 bool HasReturnStmt = false; 375 // Control flow is broken if we are extracting a break/continue without a 376 // corresponding parent loop/switch 377 bool BrokenControlFlow = false; 378 // FIXME: capture TypeAliasDecl and UsingDirectiveDecl 379 // FIXME: Capture type information as well. 380 DeclInformation *createDeclInfo(const Decl *D, ZoneRelative RelativeLoc); 381 DeclInformation *getDeclInfoFor(const Decl *D); 382 }; 383 384 CapturedZoneInfo::DeclInformation * 385 CapturedZoneInfo::createDeclInfo(const Decl *D, ZoneRelative RelativeLoc) { 386 // The new Decl's index is the size of the map so far. 387 auto InsertionResult = DeclInfoMap.insert( 388 {D, DeclInformation(D, RelativeLoc, DeclInfoMap.size())}); 389 // Return the newly created DeclInfo 390 return &InsertionResult.first->second; 391 } 392 393 CapturedZoneInfo::DeclInformation * 394 CapturedZoneInfo::getDeclInfoFor(const Decl *D) { 395 // If the Decl doesn't exist, we 396 auto Iter = DeclInfoMap.find(D); 397 if (Iter == DeclInfoMap.end()) 398 return nullptr; 399 return &Iter->second; 400 } 401 402 void CapturedZoneInfo::DeclInformation::markOccurence( 403 ZoneRelative ReferenceLoc) { 404 switch (ReferenceLoc) { 405 case ZoneRelative::Inside: 406 IsReferencedInZone = true; 407 break; 408 case ZoneRelative::After: 409 IsReferencedInPostZone = true; 410 break; 411 default: 412 break; 413 } 414 } 415 416 bool isLoop(const Stmt *S) { 417 return isa<ForStmt>(S) || isa<DoStmt>(S) || isa<WhileStmt>(S) || 418 isa<CXXForRangeStmt>(S); 419 } 420 421 // Captures information from Extraction Zone 422 CapturedZoneInfo captureZoneInfo(const ExtractionZone &ExtZone) { 423 // We use the ASTVisitor instead of using the selection tree since we need to 424 // find references in the PostZone as well. 425 // FIXME: Check which statements we don't allow to extract. 426 class ExtractionZoneVisitor 427 : public clang::RecursiveASTVisitor<ExtractionZoneVisitor> { 428 public: 429 ExtractionZoneVisitor(const ExtractionZone &ExtZone) : ExtZone(ExtZone) { 430 TraverseDecl(const_cast<FunctionDecl *>(ExtZone.EnclosingFunction)); 431 } 432 433 bool TraverseStmt(Stmt *S) { 434 if (!S) 435 return true; 436 bool IsRootStmt = ExtZone.isRootStmt(const_cast<const Stmt *>(S)); 437 // If we are starting traversal of a RootStmt, we are somewhere inside 438 // ExtractionZone 439 if (IsRootStmt) 440 CurrentLocation = ZoneRelative::Inside; 441 addToLoopSwitchCounters(S, 1); 442 // Traverse using base class's TraverseStmt 443 RecursiveASTVisitor::TraverseStmt(S); 444 addToLoopSwitchCounters(S, -1); 445 // We set the current location as after since next stmt will either be a 446 // RootStmt (handled at the beginning) or after extractionZone 447 if (IsRootStmt) 448 CurrentLocation = ZoneRelative::After; 449 return true; 450 } 451 452 // Add Increment to CurNumberOf{Loops,Switch} if statement is 453 // {Loop,Switch} and inside Extraction Zone. 454 void addToLoopSwitchCounters(Stmt *S, int Increment) { 455 if (CurrentLocation != ZoneRelative::Inside) 456 return; 457 if (isLoop(S)) 458 CurNumberOfNestedLoops += Increment; 459 else if (isa<SwitchStmt>(S)) 460 CurNumberOfSwitch += Increment; 461 } 462 463 // Decrement CurNumberOf{NestedLoops,Switch} if statement is {Loop,Switch} 464 // and inside Extraction Zone. 465 void decrementLoopSwitchCounters(Stmt *S) { 466 if (CurrentLocation != ZoneRelative::Inside) 467 return; 468 if (isLoop(S)) 469 CurNumberOfNestedLoops--; 470 else if (isa<SwitchStmt>(S)) 471 CurNumberOfSwitch--; 472 } 473 474 bool VisitDecl(Decl *D) { 475 Info.createDeclInfo(D, CurrentLocation); 476 return true; 477 } 478 479 bool VisitDeclRefExpr(DeclRefExpr *DRE) { 480 // Find the corresponding Decl and mark it's occurence. 481 const Decl *D = DRE->getDecl(); 482 auto *DeclInfo = Info.getDeclInfoFor(D); 483 // If no Decl was found, the Decl must be outside the enclosingFunc. 484 if (!DeclInfo) 485 DeclInfo = Info.createDeclInfo(D, ZoneRelative::OutsideFunc); 486 DeclInfo->markOccurence(CurrentLocation); 487 // FIXME: check if reference mutates the Decl being referred. 488 return true; 489 } 490 491 bool VisitReturnStmt(ReturnStmt *Return) { 492 if (CurrentLocation == ZoneRelative::Inside) 493 Info.HasReturnStmt = true; 494 return true; 495 } 496 497 bool VisitBreakStmt(BreakStmt *Break) { 498 // Control flow is broken if break statement is selected without any 499 // parent loop or switch statement. 500 if (CurrentLocation == ZoneRelative::Inside && 501 !(CurNumberOfNestedLoops || CurNumberOfSwitch)) 502 Info.BrokenControlFlow = true; 503 return true; 504 } 505 506 bool VisitContinueStmt(ContinueStmt *Continue) { 507 // Control flow is broken if Continue statement is selected without any 508 // parent loop 509 if (CurrentLocation == ZoneRelative::Inside && !CurNumberOfNestedLoops) 510 Info.BrokenControlFlow = true; 511 return true; 512 } 513 CapturedZoneInfo Info; 514 const ExtractionZone &ExtZone; 515 ZoneRelative CurrentLocation = ZoneRelative::Before; 516 // Number of {loop,switch} statements that are currently in the traversal 517 // stack inside Extraction Zone. Used to check for broken control flow. 518 unsigned CurNumberOfNestedLoops = 0; 519 unsigned CurNumberOfSwitch = 0; 520 }; 521 ExtractionZoneVisitor Visitor(ExtZone); 522 return std::move(Visitor.Info); 523 } 524 525 // Adds parameters to ExtractedFunc. 526 // Returns true if able to find the parameters successfully and no hoisting 527 // needed. 528 // FIXME: Check if the declaration has a local/anonymous type 529 bool createParameters(NewFunction &ExtractedFunc, 530 const CapturedZoneInfo &CapturedInfo) { 531 for (const auto &KeyVal : CapturedInfo.DeclInfoMap) { 532 const auto &DeclInfo = KeyVal.second; 533 // If a Decl was Declared in zone and referenced in post zone, it 534 // needs to be hoisted (we bail out in that case). 535 // FIXME: Support Decl Hoisting. 536 if (DeclInfo.DeclaredIn == ZoneRelative::Inside && 537 DeclInfo.IsReferencedInPostZone) 538 return false; 539 if (!DeclInfo.IsReferencedInZone) 540 continue; // no need to pass as parameter, not referenced 541 if (DeclInfo.DeclaredIn == ZoneRelative::Inside || 542 DeclInfo.DeclaredIn == ZoneRelative::OutsideFunc) 543 continue; // no need to pass as parameter, still accessible. 544 // Parameter specific checks. 545 const ValueDecl *VD = dyn_cast_or_null<ValueDecl>(DeclInfo.TheDecl); 546 // Can't parameterise if the Decl isn't a ValueDecl or is a FunctionDecl 547 // (this includes the case of recursive call to EnclosingFunc in Zone). 548 if (!VD || isa<FunctionDecl>(DeclInfo.TheDecl)) 549 return false; 550 // Parameter qualifiers are same as the Decl's qualifiers. 551 QualType TypeInfo = VD->getType().getNonReferenceType(); 552 // FIXME: Need better qualifier checks: check mutated status for 553 // Decl(e.g. was it assigned, passed as nonconst argument, etc) 554 // FIXME: check if parameter will be a non l-value reference. 555 // FIXME: We don't want to always pass variables of types like int, 556 // pointers, etc by reference. 557 bool IsPassedByReference = true; 558 // We use the index of declaration as the ordering priority for parameters. 559 ExtractedFunc.Parameters.push_back( 560 {VD->getName(), TypeInfo, IsPassedByReference, DeclInfo.DeclIndex}); 561 } 562 llvm::sort(ExtractedFunc.Parameters); 563 return true; 564 } 565 566 // Clangd uses open ranges while ExtractionSemicolonPolicy (in Clang Tooling) 567 // uses closed ranges. Generates the semicolon policy for the extraction and 568 // extends the ZoneRange if necessary. 569 tooling::ExtractionSemicolonPolicy 570 getSemicolonPolicy(ExtractionZone &ExtZone, const SourceManager &SM, 571 const LangOptions &LangOpts) { 572 // Get closed ZoneRange. 573 SourceRange FuncBodyRange = {ExtZone.ZoneRange.getBegin(), 574 ExtZone.ZoneRange.getEnd().getLocWithOffset(-1)}; 575 auto SemicolonPolicy = tooling::ExtractionSemicolonPolicy::compute( 576 ExtZone.getLastRootStmt()->ASTNode.get<Stmt>(), FuncBodyRange, SM, 577 LangOpts); 578 // Update ZoneRange. 579 ExtZone.ZoneRange.setEnd(FuncBodyRange.getEnd().getLocWithOffset(1)); 580 return SemicolonPolicy; 581 } 582 583 // Generate return type for ExtractedFunc. Return false if unable to do so. 584 bool generateReturnProperties(NewFunction &ExtractedFunc, 585 const CapturedZoneInfo &CapturedInfo) { 586 587 // FIXME: Use Existing Return statements (if present) 588 // FIXME: Generate new return statement if needed. 589 if (CapturedInfo.HasReturnStmt) 590 return false; 591 ExtractedFunc.ReturnType = "void"; 592 return true; 593 } 594 595 // FIXME: add support for adding other function return types besides void. 596 // FIXME: assign the value returned by non void extracted function. 597 llvm::Expected<NewFunction> getExtractedFunction(ExtractionZone &ExtZone, 598 const SourceManager &SM, 599 const LangOptions &LangOpts) { 600 CapturedZoneInfo CapturedInfo = captureZoneInfo(ExtZone); 601 // Bail out if any break of continue exists 602 if (CapturedInfo.BrokenControlFlow) 603 return llvm::createStringError(llvm::inconvertibleErrorCode(), 604 +"Cannot extract break/continue without " 605 "corresponding loop/switch statement."); 606 NewFunction ExtractedFunc(getSemicolonPolicy(ExtZone, SM, LangOpts)); 607 ExtractedFunc.BodyRange = ExtZone.ZoneRange; 608 ExtractedFunc.InsertionPoint = ExtZone.getInsertionPoint(); 609 ExtractedFunc.EnclosingFuncContext = 610 ExtZone.EnclosingFunction->getDeclContext(); 611 if (!createParameters(ExtractedFunc, CapturedInfo) || 612 !generateReturnProperties(ExtractedFunc, CapturedInfo)) 613 return llvm::createStringError(llvm::inconvertibleErrorCode(), 614 +"Too complex to extract."); 615 return ExtractedFunc; 616 } 617 618 class ExtractFunction : public Tweak { 619 public: 620 const char *id() const override final; 621 bool prepare(const Selection &Inputs) override; 622 Expected<Effect> apply(const Selection &Inputs) override; 623 std::string title() const override { return "Extract to function"; } 624 Intent intent() const override { return Refactor; } 625 626 private: 627 ExtractionZone ExtZone; 628 }; 629 630 REGISTER_TWEAK(ExtractFunction) 631 tooling::Replacement replaceWithFuncCall(const NewFunction &ExtractedFunc, 632 const SourceManager &SM, 633 const LangOptions &LangOpts) { 634 std::string FuncCall = ExtractedFunc.renderCall(); 635 return tooling::Replacement( 636 SM, CharSourceRange(ExtractedFunc.BodyRange, false), FuncCall, LangOpts); 637 } 638 639 tooling::Replacement createFunctionDefinition(const NewFunction &ExtractedFunc, 640 const SourceManager &SM) { 641 std::string FunctionDef = ExtractedFunc.renderDefinition(SM); 642 return tooling::Replacement(SM, ExtractedFunc.InsertionPoint, 0, FunctionDef); 643 } 644 645 bool ExtractFunction::prepare(const Selection &Inputs) { 646 const Node *CommonAnc = Inputs.ASTSelection.commonAncestor(); 647 const SourceManager &SM = Inputs.AST.getSourceManager(); 648 const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts(); 649 if (auto MaybeExtZone = findExtractionZone(CommonAnc, SM, LangOpts)) { 650 ExtZone = std::move(*MaybeExtZone); 651 return true; 652 } 653 return false; 654 } 655 656 Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) { 657 const SourceManager &SM = Inputs.AST.getSourceManager(); 658 const LangOptions &LangOpts = Inputs.AST.getASTContext().getLangOpts(); 659 auto ExtractedFunc = getExtractedFunction(ExtZone, SM, LangOpts); 660 // FIXME: Add more types of errors. 661 if (!ExtractedFunc) 662 return ExtractedFunc.takeError(); 663 tooling::Replacements Result; 664 if (auto Err = Result.add(createFunctionDefinition(*ExtractedFunc, SM))) 665 return std::move(Err); 666 if (auto Err = Result.add(replaceWithFuncCall(*ExtractedFunc, SM, LangOpts))) 667 return std::move(Err); 668 return Effect::mainFileEdit(SM, std::move(Result)); 669 } 670 671 } // namespace 672 } // namespace clangd 673 } // namespace clang 674