diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2017-12-18 20:11:37 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2017-12-18 20:11:37 +0000 |
commit | 461a67fa15370a9ec88f8f8a240bf7c123bb2029 (patch) | |
tree | 6942083d7d56bba40ec790a453ca58ad3baf6832 /lib/Tooling/Refactoring | |
parent | 75c3240472ba6ac2669ee72ca67eb72d4e2851fc (diff) |
Vendor import of clang trunk r321017:vendor/clang/clang-trunk-r321017
Notes
Notes:
svn path=/vendor/clang/dist/; revision=326941
svn path=/vendor/clang/clang-trunk-r321017/; revision=326942; tag=vendor/clang/clang-trunk-r321017
Diffstat (limited to 'lib/Tooling/Refactoring')
-rw-r--r-- | lib/Tooling/Refactoring/ASTSelection.cpp | 453 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/ASTSelectionRequirements.cpp | 48 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/AtomicChange.cpp | 188 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/CMakeLists.txt | 13 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Extract/Extract.cpp | 199 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Extract/SourceExtraction.cpp | 112 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Extract/SourceExtraction.h | 52 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/RefactoringActions.cpp | 114 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Rename/RenamingAction.cpp | 197 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp | 37 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Rename/USRFindingAction.cpp | 54 | ||||
-rw-r--r-- | lib/Tooling/Refactoring/Rename/USRLocFinder.cpp | 298 |
12 files changed, 1644 insertions, 121 deletions
diff --git a/lib/Tooling/Refactoring/ASTSelection.cpp b/lib/Tooling/Refactoring/ASTSelection.cpp new file mode 100644 index 000000000000..7123fc32cec9 --- /dev/null +++ b/lib/Tooling/Refactoring/ASTSelection.cpp @@ -0,0 +1,453 @@ +//===--- ASTSelection.cpp - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/ASTSelection.h" +#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/SaveAndRestore.h" + +using namespace clang; +using namespace tooling; +using ast_type_traits::DynTypedNode; + +namespace { + +CharSourceRange getLexicalDeclRange(Decl *D, const SourceManager &SM, + const LangOptions &LangOpts) { + if (!isa<ObjCImplDecl>(D)) + return CharSourceRange::getTokenRange(D->getSourceRange()); + // Objective-C implementation declarations end at the '@' instead of the 'end' + // keyword. Use the lexer to find the location right after 'end'. + SourceRange R = D->getSourceRange(); + SourceLocation LocAfterEnd = Lexer::findLocationAfterToken( + R.getEnd(), tok::raw_identifier, SM, LangOpts, + /*SkipTrailingWhitespaceAndNewLine=*/false); + return LocAfterEnd.isValid() + ? CharSourceRange::getCharRange(R.getBegin(), LocAfterEnd) + : CharSourceRange::getTokenRange(R); +} + +/// Constructs the tree of selected AST nodes that either contain the location +/// of the cursor or overlap with the selection range. +class ASTSelectionFinder + : public LexicallyOrderedRecursiveASTVisitor<ASTSelectionFinder> { +public: + ASTSelectionFinder(SourceRange Selection, FileID TargetFile, + const ASTContext &Context) + : LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()), + SelectionBegin(Selection.getBegin()), + SelectionEnd(Selection.getBegin() == Selection.getEnd() + ? SourceLocation() + : Selection.getEnd()), + TargetFile(TargetFile), Context(Context) { + // The TU decl is the root of the selected node tree. + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()), + SourceSelectionKind::None)); + } + + Optional<SelectedASTNode> getSelectedASTNode() { + assert(SelectionStack.size() == 1 && "stack was not popped"); + SelectedASTNode Result = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (Result.Children.empty()) + return None; + return std::move(Result); + } + + bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { + // Avoid traversing the semantic expressions. They should be handled by + // looking through the appropriate opaque expressions in order to build + // a meaningful selection tree. + llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, true); + return TraverseStmt(E->getSyntacticForm()); + } + + bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) { + if (!LookThroughOpaqueValueExprs) + return true; + llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, false); + return TraverseStmt(E->getSourceExpr()); + } + + bool TraverseDecl(Decl *D) { + if (isa<TranslationUnitDecl>(D)) + return LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); + if (D->isImplicit()) + return true; + + // Check if this declaration is written in the file of interest. + const SourceRange DeclRange = D->getSourceRange(); + const SourceManager &SM = Context.getSourceManager(); + SourceLocation FileLoc; + if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID()) + FileLoc = DeclRange.getEnd(); + else + FileLoc = SM.getSpellingLoc(DeclRange.getBegin()); + if (SM.getFileID(FileLoc) != TargetFile) + return true; + + SourceSelectionKind SelectionKind = + selectionKindFor(getLexicalDeclRange(D, SM, Context.getLangOpts())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*D), SelectionKind)); + LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); + popAndAddToSelectionIfSelected(SelectionKind); + + if (DeclRange.getEnd().isValid() && + SM.isBeforeInTranslationUnit(SelectionEnd.isValid() ? SelectionEnd + : SelectionBegin, + DeclRange.getEnd())) { + // Stop early when we've reached a declaration after the selection. + return false; + } + return true; + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return true; + if (auto *Opaque = dyn_cast<OpaqueValueExpr>(S)) + return TraverseOpaqueValueExpr(Opaque); + // Avoid selecting implicit 'this' expressions. + if (auto *TE = dyn_cast<CXXThisExpr>(S)) { + if (TE->isImplicit()) + return true; + } + // FIXME (Alex Lorenz): Improve handling for macro locations. + SourceSelectionKind SelectionKind = + selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*S), SelectionKind)); + LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S); + popAndAddToSelectionIfSelected(SelectionKind); + return true; + } + +private: + void popAndAddToSelectionIfSelected(SourceSelectionKind SelectionKind) { + SelectedASTNode Node = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty()) + SelectionStack.back().Children.push_back(std::move(Node)); + } + + SourceSelectionKind selectionKindFor(CharSourceRange Range) { + SourceLocation End = Range.getEnd(); + const SourceManager &SM = Context.getSourceManager(); + if (Range.isTokenRange()) + End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts()); + if (!SourceLocation::isPairOfFileLocations(Range.getBegin(), End)) + return SourceSelectionKind::None; + if (!SelectionEnd.isValid()) { + // Do a quick check when the selection is of length 0. + if (SM.isPointWithin(SelectionBegin, Range.getBegin(), End)) + return SourceSelectionKind::ContainsSelection; + return SourceSelectionKind::None; + } + bool HasStart = SM.isPointWithin(SelectionBegin, Range.getBegin(), End); + bool HasEnd = SM.isPointWithin(SelectionEnd, Range.getBegin(), End); + if (HasStart && HasEnd) + return SourceSelectionKind::ContainsSelection; + if (SM.isPointWithin(Range.getBegin(), SelectionBegin, SelectionEnd) && + SM.isPointWithin(End, SelectionBegin, SelectionEnd)) + return SourceSelectionKind::InsideSelection; + // Ensure there's at least some overlap with the 'start'/'end' selection + // types. + if (HasStart && SelectionBegin != End) + return SourceSelectionKind::ContainsSelectionStart; + if (HasEnd && SelectionEnd != Range.getBegin()) + return SourceSelectionKind::ContainsSelectionEnd; + + return SourceSelectionKind::None; + } + + const SourceLocation SelectionBegin, SelectionEnd; + FileID TargetFile; + const ASTContext &Context; + std::vector<SelectedASTNode> SelectionStack; + /// Controls whether we can traverse through the OpaqueValueExpr. This is + /// typically enabled during the traversal of syntactic form for + /// PseudoObjectExprs. + bool LookThroughOpaqueValueExprs = false; +}; + +} // end anonymous namespace + +Optional<SelectedASTNode> +clang::tooling::findSelectedASTNodes(const ASTContext &Context, + SourceRange SelectionRange) { + assert(SelectionRange.isValid() && + SourceLocation::isPairOfFileLocations(SelectionRange.getBegin(), + SelectionRange.getEnd()) && + "Expected a file range"); + FileID TargetFile = + Context.getSourceManager().getFileID(SelectionRange.getBegin()); + assert(Context.getSourceManager().getFileID(SelectionRange.getEnd()) == + TargetFile && + "selection range must span one file"); + + ASTSelectionFinder Visitor(SelectionRange, TargetFile, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + return Visitor.getSelectedASTNode(); +} + +static const char *selectionKindToString(SourceSelectionKind Kind) { + switch (Kind) { + case SourceSelectionKind::None: + return "none"; + case SourceSelectionKind::ContainsSelection: + return "contains-selection"; + case SourceSelectionKind::ContainsSelectionStart: + return "contains-selection-start"; + case SourceSelectionKind::ContainsSelectionEnd: + return "contains-selection-end"; + case SourceSelectionKind::InsideSelection: + return "inside"; + } + llvm_unreachable("invalid selection kind"); +} + +static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS, + unsigned Indent = 0) { + OS.indent(Indent * 2); + if (const Decl *D = Node.Node.get<Decl>()) { + OS << D->getDeclKindName() << "Decl"; + if (const auto *ND = dyn_cast<NamedDecl>(D)) + OS << " \"" << ND->getNameAsString() << '"'; + } else if (const Stmt *S = Node.Node.get<Stmt>()) { + OS << S->getStmtClassName(); + } + OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n"; + for (const auto &Child : Node.Children) + dump(Child, OS, Indent + 1); +} + +void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); } + +/// Returns true if the given node has any direct children with the following +/// selection kind. +/// +/// Note: The direct children also include children of direct children with the +/// "None" selection kind. +static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node, + SourceSelectionKind Kind) { + assert(Kind != SourceSelectionKind::None && "invalid predicate!"); + for (const auto &Child : Node.Children) { + if (Child.SelectionKind == Kind) + return true; + if (Child.SelectionKind == SourceSelectionKind::None) + return hasAnyDirectChildrenWithKind(Child, Kind); + } + return false; +} + +namespace { +struct SelectedNodeWithParents { + SelectedNodeWithParents(SelectedNodeWithParents &&) = default; + SelectedNodeWithParents &operator=(SelectedNodeWithParents &&) = default; + SelectedASTNode::ReferenceType Node; + llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents; + + /// Canonicalizes the given selection by selecting different related AST nodes + /// when it makes sense to do so. + void canonicalize(); +}; + +enum SelectionCanonicalizationAction { KeepSelection, SelectParent }; + +/// Returns the canonicalization action which should be applied to the +/// selected statement. +SelectionCanonicalizationAction +getSelectionCanonizalizationAction(const Stmt *S, const Stmt *Parent) { + // Select the parent expression when: + // - The string literal in ObjC string literal is selected, e.g.: + // @"test" becomes @"test" + // ~~~~~~ ~~~~~~~ + if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent)) + return SelectParent; + // The entire call should be selected when just the member expression + // that refers to the method or the decl ref that refers to the function + // is selected. + // f.call(args) becomes f.call(args) + // ~~~~ ~~~~~~~~~~~~ + // func(args) becomes func(args) + // ~~~~ ~~~~~~~~~~ + else if (const auto *CE = dyn_cast<CallExpr>(Parent)) { + if ((isa<MemberExpr>(S) || isa<DeclRefExpr>(S)) && + CE->getCallee()->IgnoreImpCasts() == S) + return SelectParent; + } + // FIXME: Syntactic form -> Entire pseudo-object expr. + return KeepSelection; +} + +} // end anonymous namespace + +void SelectedNodeWithParents::canonicalize() { + const Stmt *S = Node.get().Node.get<Stmt>(); + assert(S && "non statement selection!"); + const Stmt *Parent = Parents[Parents.size() - 1].get().Node.get<Stmt>(); + if (!Parent) + return; + + // Look through the implicit casts in the parents. + unsigned ParentIndex = 1; + for (; (ParentIndex + 1) <= Parents.size() && isa<ImplicitCastExpr>(Parent); + ++ParentIndex) { + const Stmt *NewParent = + Parents[Parents.size() - ParentIndex - 1].get().Node.get<Stmt>(); + if (!NewParent) + break; + Parent = NewParent; + } + + switch (getSelectionCanonizalizationAction(S, Parent)) { + case SelectParent: + Node = Parents[Parents.size() - ParentIndex]; + for (; ParentIndex != 0; --ParentIndex) + Parents.pop_back(); + break; + case KeepSelection: + break; + } +} + +/// Finds the set of bottom-most selected AST nodes that are in the selection +/// tree with the specified selection kind. +/// +/// For example, given the following selection tree: +/// +/// FunctionDecl "f" contains-selection +/// CompoundStmt contains-selection [#1] +/// CallExpr inside +/// ImplicitCastExpr inside +/// DeclRefExpr inside +/// IntegerLiteral inside +/// IntegerLiteral inside +/// FunctionDecl "f2" contains-selection +/// CompoundStmt contains-selection [#2] +/// CallExpr inside +/// ImplicitCastExpr inside +/// DeclRefExpr inside +/// IntegerLiteral inside +/// IntegerLiteral inside +/// +/// This function will find references to nodes #1 and #2 when searching for the +/// \c ContainsSelection kind. +static void findDeepestWithKind( + const SelectedASTNode &ASTSelection, + llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes, + SourceSelectionKind Kind, + llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) { + if (ASTSelection.Node.get<DeclStmt>()) { + // Select the entire decl stmt when any of its child declarations is the + // bottom-most. + for (const auto &Child : ASTSelection.Children) { + if (!hasAnyDirectChildrenWithKind(Child, Kind)) { + MatchingNodes.push_back(SelectedNodeWithParents{ + std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}}); + return; + } + } + } else { + if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) { + // This node is the bottom-most. + MatchingNodes.push_back(SelectedNodeWithParents{ + std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}}); + return; + } + } + // Search in the children. + ParentStack.push_back(std::cref(ASTSelection)); + for (const auto &Child : ASTSelection.Children) + findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack); + ParentStack.pop_back(); +} + +static void findDeepestWithKind( + const SelectedASTNode &ASTSelection, + llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes, + SourceSelectionKind Kind) { + llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack; + findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack); +} + +Optional<CodeRangeASTSelection> +CodeRangeASTSelection::create(SourceRange SelectionRange, + const SelectedASTNode &ASTSelection) { + // Code range is selected when the selection range is not empty. + if (SelectionRange.getBegin() == SelectionRange.getEnd()) + return None; + llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection; + findDeepestWithKind(ASTSelection, ContainSelection, + SourceSelectionKind::ContainsSelection); + // We are looking for a selection in one body of code, so let's focus on + // one matching result. + if (ContainSelection.size() != 1) + return None; + SelectedNodeWithParents &Selected = ContainSelection[0]; + if (!Selected.Node.get().Node.get<Stmt>()) + return None; + const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>(); + if (!isa<CompoundStmt>(CodeRangeStmt)) { + Selected.canonicalize(); + return CodeRangeASTSelection(Selected.Node, Selected.Parents, + /*AreChildrenSelected=*/false); + } + // FIXME (Alex L): First selected SwitchCase means that first case statement. + // is selected actually + // (See https://github.com/apple/swift-clang & CompoundStmtRange). + + // FIXME (Alex L): Tweak selection rules for compound statements, see: + // https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/ + // Refactor/ASTSlice.cpp#L513 + // The user selected multiple statements in a compound statement. + Selected.Parents.push_back(Selected.Node); + return CodeRangeASTSelection(Selected.Node, Selected.Parents, + /*AreChildrenSelected=*/true); +} + +static bool isFunctionLikeDeclaration(const Decl *D) { + // FIXME (Alex L): Test for BlockDecl. + return isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D); +} + +bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const { + bool IsPrevCompound = false; + // Scan through the parents (bottom-to-top) and check if the selection is + // contained in a compound statement that's a body of a function/method + // declaration. + for (const auto &Parent : llvm::reverse(Parents)) { + const DynTypedNode &Node = Parent.get().Node; + if (const auto *D = Node.get<Decl>()) { + if (isFunctionLikeDeclaration(D)) + return IsPrevCompound; + // Stop the search at any type declaration to avoid returning true for + // expressions in type declarations in functions, like: + // function foo() { struct X { + // int m = /*selection:*/ 1 + 2 /*selection end*/; }; }; + if (isa<TypeDecl>(D)) + return false; + } + IsPrevCompound = Node.get<CompoundStmt>() != nullptr; + } + return false; +} + +const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const { + for (const auto &Parent : llvm::reverse(Parents)) { + const DynTypedNode &Node = Parent.get().Node; + if (const auto *D = Node.get<Decl>()) { + if (isFunctionLikeDeclaration(D)) + return D; + } + } + return nullptr; +} diff --git a/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp new file mode 100644 index 000000000000..c0232c5da442 --- /dev/null +++ b/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp @@ -0,0 +1,48 @@ +//===--- ASTSelectionRequirements.cpp - Clang refactoring library ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" + +using namespace clang; +using namespace tooling; + +Expected<SelectedASTNode> +ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const { + // FIXME: Memoize so that selection is evaluated only once. + Expected<SourceRange> Range = + SourceRangeSelectionRequirement::evaluate(Context); + if (!Range) + return Range.takeError(); + + Optional<SelectedASTNode> Selection = + findSelectedASTNodes(Context.getASTContext(), *Range); + if (!Selection) + return Context.createDiagnosticError( + Range->getBegin(), diag::err_refactor_selection_invalid_ast); + return std::move(*Selection); +} + +Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate( + RefactoringRuleContext &Context) const { + // FIXME: Memoize so that selection is evaluated only once. + Expected<SelectedASTNode> ASTSelection = + ASTSelectionRequirement::evaluate(Context); + if (!ASTSelection) + return ASTSelection.takeError(); + std::unique_ptr<SelectedASTNode> StoredSelection = + llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection)); + Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create( + Context.getSelectionRange(), *StoredSelection); + if (!CodeRange) + return Context.createDiagnosticError( + Context.getSelectionRange().getBegin(), + diag::err_refactor_selection_invalid_ast); + Context.setASTSelection(std::move(StoredSelection)); + return std::move(*CodeRange); +} diff --git a/lib/Tooling/Refactoring/AtomicChange.cpp b/lib/Tooling/Refactoring/AtomicChange.cpp index 79dd346acf72..e4cc6a5617b6 100644 --- a/lib/Tooling/Refactoring/AtomicChange.cpp +++ b/lib/Tooling/Refactoring/AtomicChange.cpp @@ -83,6 +83,116 @@ template <> struct MappingTraits<clang::tooling::AtomicChange> { namespace clang { namespace tooling { +namespace { + +// Returns true if there is any line that violates \p ColumnLimit in range +// [Start, End]. +bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit, + unsigned Start, unsigned End) { + auto StartPos = Code.rfind('\n', Start); + StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1; + + auto EndPos = Code.find("\n", End); + if (EndPos == llvm::StringRef::npos) + EndPos = Code.size(); + + llvm::SmallVector<llvm::StringRef, 8> Lines; + Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n'); + for (llvm::StringRef Line : Lines) + if (Line.size() > ColumnLimit) + return true; + return false; +} + +std::vector<Range> +getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit, + ApplyChangesSpec::FormatOption Format, + const clang::tooling::Replacements &Replaces) { + // kNone suppresses formatting entirely. + if (Format == ApplyChangesSpec::kNone) + return {}; + std::vector<clang::tooling::Range> Ranges; + // This works assuming that replacements are ordered by offset. + // FIXME: use `getAffectedRanges()` to calculate when it does not include '\n' + // at the end of an insertion in affected ranges. + int Offset = 0; + for (const clang::tooling::Replacement &R : Replaces) { + int Start = R.getOffset() + Offset; + int End = Start + R.getReplacementText().size(); + if (!R.getReplacementText().empty() && + R.getReplacementText().back() == '\n' && R.getLength() == 0 && + R.getOffset() > 0 && R.getOffset() <= Code.size() && + Code[R.getOffset() - 1] == '\n') + // If we are inserting at the start of a line and the replacement ends in + // a newline, we don't need to format the subsequent line. + --End; + Offset += R.getReplacementText().size() - R.getLength(); + + if (Format == ApplyChangesSpec::kAll || + violatesColumnLimit(Code, ColumnLimit, Start, End)) + Ranges.emplace_back(Start, End - Start); + } + return Ranges; +} + +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error<llvm::StringError>(Message, + llvm::inconvertibleErrorCode()); +} + +// Creates replacements for inserting/deleting #include headers. +llvm::Expected<Replacements> +createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code, + llvm::ArrayRef<AtomicChange> Changes, + const format::FormatStyle &Style) { + // Create header insertion/deletion replacements to be cleaned up + // (i.e. converted to real insertion/deletion replacements). + Replacements HeaderReplacements; + for (const auto &Change : Changes) { + for (llvm::StringRef Header : Change.getInsertedHeaders()) { + std::string EscapedHeader = + Header.startswith("<") || Header.startswith("\"") + ? Header.str() + : ("\"" + Header + "\"").str(); + std::string ReplacementText = "#include " + EscapedHeader; + // Offset UINT_MAX and length 0 indicate that the replacement is a header + // insertion. + llvm::Error Err = HeaderReplacements.add( + tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText)); + if (Err) + return std::move(Err); + } + for (const std::string &Header : Change.getRemovedHeaders()) { + // Offset UINT_MAX and length 1 indicate that the replacement is a header + // deletion. + llvm::Error Err = + HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header)); + if (Err) + return std::move(Err); + } + } + + // cleanupAroundReplacements() converts header insertions/deletions into + // actual replacements that add/remove headers at the right location. + return clang::format::cleanupAroundReplacements(Code, HeaderReplacements, + Style); +} + +// Combine replacements in all Changes as a `Replacements`. This ignores the +// file path in all replacements and replaces them with \p FilePath. +llvm::Expected<Replacements> +combineReplacementsInChanges(llvm::StringRef FilePath, + llvm::ArrayRef<AtomicChange> Changes) { + Replacements Replaces; + for (const auto &Change : Changes) + for (const auto &R : Change.getReplacements()) + if (auto Err = Replaces.add(Replacement( + FilePath, R.getOffset(), R.getLength(), R.getReplacementText()))) + return std::move(Err); + return Replaces; +} + +} // end namespace AtomicChange::AtomicChange(const SourceManager &SM, SourceLocation KeyPosition) { @@ -105,6 +215,15 @@ AtomicChange::AtomicChange(std::string Key, std::string FilePath, RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) { } +bool AtomicChange::operator==(const AtomicChange &Other) const { + if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error) + return false; + if (!(Replaces == Other.Replaces)) + return false; + // FXIME: Compare header insertions/removals. + return true; +} + std::string AtomicChange::toYAMLString() { std::string YamlContent; llvm::raw_string_ostream YamlContentStream(YamlContent); @@ -173,5 +292,74 @@ void AtomicChange::removeHeader(llvm::StringRef Header) { RemovedHeaders.push_back(Header); } +llvm::Expected<std::string> +applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code, + llvm::ArrayRef<AtomicChange> Changes, + const ApplyChangesSpec &Spec) { + llvm::Expected<Replacements> HeaderReplacements = + createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style); + if (!HeaderReplacements) + return make_string_error( + "Failed to create replacements for header changes: " + + llvm::toString(HeaderReplacements.takeError())); + + llvm::Expected<Replacements> Replaces = + combineReplacementsInChanges(FilePath, Changes); + if (!Replaces) + return make_string_error("Failed to combine replacements in all changes: " + + llvm::toString(Replaces.takeError())); + + Replacements AllReplaces = std::move(*Replaces); + for (const auto &R : *HeaderReplacements) { + llvm::Error Err = AllReplaces.add(R); + if (Err) + return make_string_error( + "Failed to combine existing replacements with header replacements: " + + llvm::toString(std::move(Err))); + } + + if (Spec.Cleanup) { + llvm::Expected<Replacements> CleanReplaces = + format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style); + if (!CleanReplaces) + return make_string_error("Failed to cleanup around replacements: " + + llvm::toString(CleanReplaces.takeError())); + AllReplaces = std::move(*CleanReplaces); + } + + // Apply all replacements. + llvm::Expected<std::string> ChangedCode = + applyAllReplacements(Code, AllReplaces); + if (!ChangedCode) + return make_string_error("Failed to apply all replacements: " + + llvm::toString(ChangedCode.takeError())); + + // Sort inserted headers. This is done even if other formatting is turned off + // as incorrectly sorted headers are always just wrong, it's not a matter of + // taste. + Replacements HeaderSortingReplacements = format::sortIncludes( + Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath); + ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements); + if (!ChangedCode) + return make_string_error( + "Failed to apply replacements for sorting includes: " + + llvm::toString(ChangedCode.takeError())); + + AllReplaces = AllReplaces.merge(HeaderSortingReplacements); + + std::vector<Range> FormatRanges = getRangesForFormating( + *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces); + if (!FormatRanges.empty()) { + Replacements FormatReplacements = + format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath); + ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements); + if (!ChangedCode) + return make_string_error( + "Failed to apply replacements for formatting changed code: " + + llvm::toString(ChangedCode.takeError())); + } + return ChangedCode; +} + } // end namespace tooling } // end namespace clang diff --git a/lib/Tooling/Refactoring/CMakeLists.txt b/lib/Tooling/Refactoring/CMakeLists.txt index 288582fc1b6b..402b5d3c6ac4 100644 --- a/lib/Tooling/Refactoring/CMakeLists.txt +++ b/lib/Tooling/Refactoring/CMakeLists.txt @@ -1,11 +1,14 @@ -set(LLVM_LINK_COMPONENTS - Option - Support - ) +set(LLVM_LINK_COMPONENTS Support) add_clang_library(clangToolingRefactor + ASTSelection.cpp + ASTSelectionRequirements.cpp AtomicChange.cpp + Extract/Extract.cpp + Extract/SourceExtraction.cpp + RefactoringActions.cpp Rename/RenamingAction.cpp + Rename/SymbolOccurrences.cpp Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp @@ -14,7 +17,9 @@ add_clang_library(clangToolingRefactor clangAST clangASTMatchers clangBasic + clangFormat clangIndex clangLex + clangRewrite clangToolingCore ) diff --git a/lib/Tooling/Refactoring/Extract/Extract.cpp b/lib/Tooling/Refactoring/Extract/Extract.cpp new file mode 100644 index 000000000000..b0847a740048 --- /dev/null +++ b/lib/Tooling/Refactoring/Extract/Extract.cpp @@ -0,0 +1,199 @@ +//===--- Extract.cpp - Clang refactoring library --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Implements the "extract" refactoring that can pull code into +/// new functions, methods or declare new variables. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Extract/Extract.h" +#include "SourceExtraction.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprObjC.h" +#include "clang/Rewrite/Core/Rewriter.h" + +namespace clang { +namespace tooling { + +namespace { + +/// Returns true if \c E is a simple literal or a reference expression that +/// should not be extracted. +bool isSimpleExpression(const Expr *E) { + if (!E) + return false; + switch (E->IgnoreParenCasts()->getStmtClass()) { + case Stmt::DeclRefExprClass: + case Stmt::PredefinedExprClass: + case Stmt::IntegerLiteralClass: + case Stmt::FloatingLiteralClass: + case Stmt::ImaginaryLiteralClass: + case Stmt::CharacterLiteralClass: + case Stmt::StringLiteralClass: + return true; + default: + return false; + } +} + +SourceLocation computeFunctionExtractionLocation(const Decl *D) { + if (isa<CXXMethodDecl>(D)) { + // Code from method that is defined in class body should be extracted to a + // function defined just before the class. + while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext())) + D = RD; + } + return D->getLocStart(); +} + +} // end anonymous namespace + +const RefactoringDescriptor &ExtractFunction::describe() { + static const RefactoringDescriptor Descriptor = { + "extract-function", + "Extract Function", + "(WIP action; use with caution!) Extracts code into a new function", + }; + return Descriptor; +} + +Expected<ExtractFunction> +ExtractFunction::initiate(RefactoringRuleContext &Context, + CodeRangeASTSelection Code, + Optional<std::string> DeclName) { + // We would like to extract code out of functions/methods/blocks. + // Prohibit extraction from things like global variable / field + // initializers and other top-level expressions. + if (!Code.isInFunctionLikeBodyOfCode()) + return Context.createDiagnosticError( + diag::err_refactor_code_outside_of_function); + + if (Code.size() == 1) { + // Avoid extraction of simple literals and references. + if (isSimpleExpression(dyn_cast<Expr>(Code[0]))) + return Context.createDiagnosticError( + diag::err_refactor_extract_simple_expression); + + // Property setters can't be extracted. + if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) { + if (!PRE->isMessagingGetter()) + return Context.createDiagnosticError( + diag::err_refactor_extract_prohibited_expression); + } + } + + return ExtractFunction(std::move(Code), DeclName); +} + +// FIXME: Support C++ method extraction. +// FIXME: Support Objective-C method extraction. +Expected<AtomicChanges> +ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) { + const Decl *ParentDecl = Code.getFunctionLikeNearestParent(); + assert(ParentDecl && "missing parent"); + + // Compute the source range of the code that should be extracted. + SourceRange ExtractedRange(Code[0]->getLocStart(), + Code[Code.size() - 1]->getLocEnd()); + // FIXME (Alex L): Add code that accounts for macro locations. + + ASTContext &AST = Context.getASTContext(); + SourceManager &SM = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + Rewriter ExtractedCodeRewriter(SM, LangOpts); + + // FIXME: Capture used variables. + + // Compute the return type. + QualType ReturnType = AST.VoidTy; + // FIXME (Alex L): Account for the return statement in extracted code. + // FIXME (Alex L): Check for lexical expression instead. + bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]); + if (IsExpr) { + // FIXME (Alex L): Get a more user-friendly type if needed. + ReturnType = cast<Expr>(Code[0])->getType(); + } + + // FIXME: Rewrite the extracted code performing any required adjustments. + + // FIXME: Capture any field if necessary (method -> function extraction). + + // FIXME: Sort captured variables by name. + + // FIXME: Capture 'this' / 'self' if necessary. + + // FIXME: Compute the actual parameter types. + + // Compute the location of the extracted declaration. + SourceLocation ExtractedDeclLocation = + computeFunctionExtractionLocation(ParentDecl); + // FIXME: Adjust the location to account for any preceding comments. + + // FIXME: Adjust with PP awareness like in Sema to get correct 'bool' + // treatment. + PrintingPolicy PP = AST.getPrintingPolicy(); + // FIXME: PP.UseStdFunctionForLambda = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute( + Code[Code.size() - 1], ExtractedRange, SM, LangOpts); + AtomicChange Change(SM, ExtractedDeclLocation); + // Create the replacement for the extracted declaration. + { + std::string ExtractedCode; + llvm::raw_string_ostream OS(ExtractedCode); + // FIXME: Use 'inline' in header. + OS << "static "; + ReturnType.print(OS, PP, DeclName); + OS << '('; + // FIXME: Arguments. + OS << ')'; + + // Function body. + OS << " {\n"; + if (IsExpr && !ReturnType->isVoidType()) + OS << "return "; + OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange); + if (Semicolons.isNeededInExtractedFunction()) + OS << ';'; + OS << "\n}\n\n"; + auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str()); + if (Err) + return std::move(Err); + } + + // Create the replacement for the call to the extracted declaration. + { + std::string ReplacedCode; + llvm::raw_string_ostream OS(ReplacedCode); + + OS << DeclName << '('; + // FIXME: Forward arguments. + OS << ')'; + if (Semicolons.isNeededInOriginalFunction()) + OS << ';'; + + auto Err = Change.replace( + SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str()); + if (Err) + return std::move(Err); + } + + // FIXME: Add support for assocciated symbol location to AtomicChange to mark + // the ranges of the name of the extracted declaration. + return AtomicChanges{std::move(Change)}; +} + +} // end namespace tooling +} // end namespace clang diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp new file mode 100644 index 000000000000..7fd8cc2d3c7f --- /dev/null +++ b/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp @@ -0,0 +1,112 @@ +//===--- SourceExtraction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SourceExtraction.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; + +namespace { + +/// Returns true if the token at the given location is a semicolon. +bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM, + LangOpts) == ";"; +} + +/// Returns true if there should be a semicolon after the given statement. +bool isSemicolonRequiredAfter(const Stmt *S) { + if (isa<CompoundStmt>(S)) + return false; + if (const auto *If = dyn_cast<IfStmt>(S)) + return isSemicolonRequiredAfter(If->getElse() ? If->getElse() + : If->getThen()); + if (const auto *While = dyn_cast<WhileStmt>(S)) + return isSemicolonRequiredAfter(While->getBody()); + if (const auto *For = dyn_cast<ForStmt>(S)) + return isSemicolonRequiredAfter(For->getBody()); + if (const auto *CXXFor = dyn_cast<CXXForRangeStmt>(S)) + return isSemicolonRequiredAfter(CXXFor->getBody()); + if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(S)) + return isSemicolonRequiredAfter(ObjCFor->getBody()); + switch (S->getStmtClass()) { + case Stmt::SwitchStmtClass: + case Stmt::CXXTryStmtClass: + case Stmt::ObjCAtSynchronizedStmtClass: + case Stmt::ObjCAutoreleasePoolStmtClass: + case Stmt::ObjCAtTryStmtClass: + return false; + default: + return true; + } +} + +/// Returns true if the two source locations are on the same line. +bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, + const SourceManager &SM) { + return !Loc1.isMacroID() && !Loc2.isMacroID() && + SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2); +} + +} // end anonymous namespace + +namespace clang { +namespace tooling { + +ExtractionSemicolonPolicy +ExtractionSemicolonPolicy::compute(const Stmt *S, SourceRange &ExtractedRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + auto neededInExtractedFunction = []() { + return ExtractionSemicolonPolicy(true, false); + }; + auto neededInOriginalFunction = []() { + return ExtractionSemicolonPolicy(false, true); + }; + + /// The extracted expression should be terminated with a ';'. The call to + /// the extracted function will replace this expression, so it won't need + /// a terminating ';'. + if (isa<Expr>(S)) + return neededInExtractedFunction(); + + /// Some statements don't need to be terminated with ';'. The call to the + /// extracted function will be a standalone statement, so it should be + /// terminated with a ';'. + bool NeedsSemi = isSemicolonRequiredAfter(S); + if (!NeedsSemi) + return neededInOriginalFunction(); + + /// Some statements might end at ';'. The extraction will move that ';', so + /// the call to the extracted function should be terminated with a ';'. + SourceLocation End = ExtractedRange.getEnd(); + if (isSemicolonAtLocation(End, SM, LangOpts)) + return neededInOriginalFunction(); + + /// Other statements should generally have a trailing ';'. We can try to find + /// it and move it together it with the extracted code. + Optional<Token> NextToken = Lexer::findNextToken(End, SM, LangOpts); + if (NextToken && NextToken->is(tok::semi) && + areOnSameLine(NextToken->getLocation(), End, SM)) { + ExtractedRange.setEnd(NextToken->getLocation()); + return neededInOriginalFunction(); + } + + /// Otherwise insert semicolons in both places. + return ExtractionSemicolonPolicy(true, true); +} + +} // end namespace tooling +} // end namespace clang diff --git a/lib/Tooling/Refactoring/Extract/SourceExtraction.h b/lib/Tooling/Refactoring/Extract/SourceExtraction.h new file mode 100644 index 000000000000..4b4bd8b477ff --- /dev/null +++ b/lib/Tooling/Refactoring/Extract/SourceExtraction.h @@ -0,0 +1,52 @@ +//===--- SourceExtraction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H +#define LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H + +#include "clang/Basic/LLVM.h" + +namespace clang { + +class LangOptions; +class SourceManager; +class SourceRange; +class Stmt; + +namespace tooling { + +/// Determines which semicolons should be inserted during extraction. +class ExtractionSemicolonPolicy { +public: + bool isNeededInExtractedFunction() const { + return IsNeededInExtractedFunction; + } + + bool isNeededInOriginalFunction() const { return IsNeededInOriginalFunction; } + + /// Returns the semicolon insertion policy that is needed for extraction of + /// the given statement from the given source range. + static ExtractionSemicolonPolicy compute(const Stmt *S, + SourceRange &ExtractedRange, + const SourceManager &SM, + const LangOptions &LangOpts); + +private: + ExtractionSemicolonPolicy(bool IsNeededInExtractedFunction, + bool IsNeededInOriginalFunction) + : IsNeededInExtractedFunction(IsNeededInExtractedFunction), + IsNeededInOriginalFunction(IsNeededInOriginalFunction) {} + bool IsNeededInExtractedFunction; + bool IsNeededInOriginalFunction; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H diff --git a/lib/Tooling/Refactoring/RefactoringActions.cpp b/lib/Tooling/Refactoring/RefactoringActions.cpp new file mode 100644 index 000000000000..37a1639cb446 --- /dev/null +++ b/lib/Tooling/Refactoring/RefactoringActions.cpp @@ -0,0 +1,114 @@ +//===--- RefactoringActions.cpp - Constructs refactoring actions ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Extract/Extract.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" + +namespace clang { +namespace tooling { + +namespace { + +class DeclNameOption final : public OptionalRefactoringOption<std::string> { +public: + StringRef getName() const { return "name"; } + StringRef getDescription() const { + return "Name of the extracted declaration"; + } +}; + +// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with +// rules. +class ExtractRefactoring final : public RefactoringAction { +public: + StringRef getCommand() const override { return "extract"; } + + StringRef getDescription() const override { + return "(WIP action; use with caution!) Extracts code into a new function"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back(createRefactoringActionRule<ExtractFunction>( + CodeRangeASTSelectionRequirement(), + OptionRequirement<DeclNameOption>())); + return Rules; + } +}; + +class OldQualifiedNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "old-qualified-name"; } + StringRef getDescription() const override { + return "The old qualified name to be renamed"; + } +}; + +class NewQualifiedNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "new-qualified-name"; } + StringRef getDescription() const override { + return "The new qualified name to change the symbol to"; + } +}; + +class NewNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "new-name"; } + StringRef getDescription() const override { + return "The new name to change the symbol to"; + } +}; + +// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with +// rules. +class LocalRename final : public RefactoringAction { +public: + StringRef getCommand() const override { return "local-rename"; } + + StringRef getDescription() const override { + return "Finds and renames symbols in code with no indexer support"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back(createRefactoringActionRule<RenameOccurrences>( + SourceRangeSelectionRequirement(), OptionRequirement<NewNameOption>())); + // FIXME: Use NewNameOption. + Rules.push_back(createRefactoringActionRule<QualifiedRenameRule>( + OptionRequirement<OldQualifiedNameOption>(), + OptionRequirement<NewQualifiedNameOption>())); + return Rules; + } +}; + +} // end anonymous namespace + +std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() { + std::vector<std::unique_ptr<RefactoringAction>> Actions; + + Actions.push_back(llvm::make_unique<LocalRename>()); + Actions.push_back(llvm::make_unique<ExtractRefactoring>()); + + return Actions; +} + +RefactoringActionRules RefactoringAction::createActiveActionRules() { + // FIXME: Filter out rules that are not supported by a particular client. + return createActionRules(); +} + +} // end namespace tooling +} // end namespace clang diff --git a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp index de6aba944a4a..c8ed9dd19a8e 100644 --- a/lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -22,8 +22,17 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h" +#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" #include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" #include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include <string> #include <vector> @@ -32,6 +41,143 @@ using namespace llvm; namespace clang { namespace tooling { +namespace { + +Expected<SymbolOccurrences> +findSymbolOccurrences(const NamedDecl *ND, RefactoringRuleContext &Context) { + std::vector<std::string> USRs = + getUSRsForDeclaration(ND, Context.getASTContext()); + std::string PrevName = ND->getNameAsString(); + return getOccurrencesOfUSRs(USRs, PrevName, + Context.getASTContext().getTranslationUnitDecl()); +} + +} // end anonymous namespace + +const RefactoringDescriptor &RenameOccurrences::describe() { + static const RefactoringDescriptor Descriptor = { + "local-rename", + "Rename", + "Finds and renames symbols in code with no indexer support", + }; + return Descriptor; +} + +Expected<RenameOccurrences> +RenameOccurrences::initiate(RefactoringRuleContext &Context, + SourceRange SelectionRange, std::string NewName) { + const NamedDecl *ND = + getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin()); + if (!ND) + return Context.createDiagnosticError( + SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol); + return RenameOccurrences(getCanonicalSymbolDeclaration(ND), + std::move(NewName)); +} + +Expected<AtomicChanges> +RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) { + Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context); + if (!Occurrences) + return Occurrences.takeError(); + // FIXME: Verify that the new name is valid. + SymbolName Name(NewName); + return createRenameReplacements( + *Occurrences, Context.getASTContext().getSourceManager(), Name); +} + +Expected<QualifiedRenameRule> +QualifiedRenameRule::initiate(RefactoringRuleContext &Context, + std::string OldQualifiedName, + std::string NewQualifiedName) { + const NamedDecl *ND = + getNamedDeclFor(Context.getASTContext(), OldQualifiedName); + if (!ND) + return llvm::make_error<llvm::StringError>("Could not find symbol " + + OldQualifiedName, + llvm::errc::invalid_argument); + return QualifiedRenameRule(ND, std::move(NewQualifiedName)); +} + +const RefactoringDescriptor &QualifiedRenameRule::describe() { + static const RefactoringDescriptor Descriptor = { + /*Name=*/"local-qualified-rename", + /*Title=*/"Qualified Rename", + /*Description=*/ + R"(Finds and renames qualified symbols in code within a translation unit. +It is used to move/rename a symbol to a new namespace/name: + * Supported symbols: classes, class members, functions, enums, and type alias. + * Renames all symbol occurrences from the old qualified name to the new + qualified name. All symbol references will be correctly qualified; For + symbol definitions, only name will be changed. +For example, rename "A::Foo" to "B::Bar": + Old code: + namespace foo { + class A {}; + } + + namespace bar { + void f(foo::A a) {} + } + + New code after rename: + namespace foo { + class B {}; + } + + namespace bar { + void f(B b) {} + })" + }; + return Descriptor; +} + +Expected<AtomicChanges> +QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) { + auto USRs = getUSRsForDeclaration(ND, Context.getASTContext()); + assert(!USRs.empty()); + return tooling::createRenameAtomicChanges( + USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl()); +} + +Expected<std::vector<AtomicChange>> +createRenameReplacements(const SymbolOccurrences &Occurrences, + const SourceManager &SM, const SymbolName &NewName) { + // FIXME: A true local rename can use just one AtomicChange. + std::vector<AtomicChange> Changes; + for (const auto &Occurrence : Occurrences) { + ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges(); + assert(NewName.getNamePieces().size() == Ranges.size() && + "Mismatching number of ranges and name pieces"); + AtomicChange Change(SM, Ranges[0].getBegin()); + for (const auto &Range : llvm::enumerate(Ranges)) { + auto Error = + Change.replace(SM, CharSourceRange::getCharRange(Range.value()), + NewName.getNamePieces()[Range.index()]); + if (Error) + return std::move(Error); + } + Changes.push_back(std::move(Change)); + } + return std::move(Changes); +} + +/// Takes each atomic change and inserts its replacements into the set of +/// replacements that belong to the appropriate file. +static void convertChangesToFileReplacements( + ArrayRef<AtomicChange> AtomicChanges, + std::map<std::string, tooling::Replacements> *FileToReplaces) { + for (const auto &AtomicChange : AtomicChanges) { + for (const auto &Replace : AtomicChange.getReplacements()) { + llvm::Error Err = (*FileToReplaces)[Replace.getFilePath()].add(Replace); + if (Err) { + llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! " + << llvm::toString(std::move(Err)) << "\n"; + } + } + } +} + class RenamingASTConsumer : public ASTConsumer { public: RenamingASTConsumer( @@ -44,37 +190,42 @@ public: FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} void HandleTranslationUnit(ASTContext &Context) override { - for (unsigned I = 0; I < NewNames.size(); ++I) + for (unsigned I = 0; I < NewNames.size(); ++I) { + // If the previous name was not found, ignore this rename request. + if (PrevNames[I].empty()) + continue; + HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]); + } } void HandleOneRename(ASTContext &Context, const std::string &NewName, const std::string &PrevName, const std::vector<std::string> &USRs) { const SourceManager &SourceMgr = Context.getSourceManager(); - std::vector<SourceLocation> RenamingCandidates; - std::vector<SourceLocation> NewCandidates; - NewCandidates = tooling::getLocationsOfUSRs( + SymbolOccurrences Occurrences = tooling::getOccurrencesOfUSRs( USRs, PrevName, Context.getTranslationUnitDecl()); - RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), - NewCandidates.end()); - - unsigned PrevNameLen = PrevName.length(); - for (const auto &Loc : RenamingCandidates) { - if (PrintLocations) { - FullSourceLoc FullLoc(Loc, SourceMgr); - errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc) + if (PrintLocations) { + for (const auto &Occurrence : Occurrences) { + FullSourceLoc FullLoc(Occurrence.getNameRanges()[0].getBegin(), + SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(FullLoc) << ":" << FullLoc.getSpellingLineNumber() << ":" << FullLoc.getSpellingColumnNumber() << "\n"; } - // FIXME: better error handling. - tooling::Replacement Replace(SourceMgr, Loc, PrevNameLen, NewName); - llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace); - if (Err) - llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! " - << llvm::toString(std::move(Err)) << "\n"; } + // FIXME: Support multi-piece names. + // FIXME: better error handling (propagate error out). + SymbolName NewNameRef(NewName); + Expected<std::vector<AtomicChange>> Change = + createRenameReplacements(Occurrences, SourceMgr, NewNameRef); + if (!Change) { + llvm::errs() << "Failed to create renaming replacements for '" << PrevName + << "'! " << llvm::toString(Change.takeError()) << "\n"; + return; + } + convertChangesToFileReplacements(*Change, &FileToReplaces); } private: @@ -103,15 +254,7 @@ public: // ready. auto AtomicChanges = tooling::createRenameAtomicChanges( USRList[I], NewNames[I], Context.getTranslationUnitDecl()); - for (const auto AtomicChange : AtomicChanges) { - for (const auto &Replace : AtomicChange.getReplacements()) { - llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace); - if (Err) { - llvm::errs() << "Renaming failed in " << Replace.getFilePath() - << "! " << llvm::toString(std::move(Err)) << "\n"; - } - } - } + convertChangesToFileReplacements(AtomicChanges, &FileToReplaces); } } diff --git a/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp new file mode 100644 index 000000000000..ea64b2c1aa8c --- /dev/null +++ b/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp @@ -0,0 +1,37 @@ +//===--- SymbolOccurrences.cpp - Clang refactoring library ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang; +using namespace tooling; + +SymbolOccurrence::SymbolOccurrence(const SymbolName &Name, OccurrenceKind Kind, + ArrayRef<SourceLocation> Locations) + : Kind(Kind) { + ArrayRef<std::string> NamePieces = Name.getNamePieces(); + assert(Locations.size() == NamePieces.size() && + "mismatching number of locations and lengths"); + assert(!Locations.empty() && "no locations"); + if (Locations.size() == 1) { + RangeOrNumRanges = SourceRange( + Locations[0], Locations[0].getLocWithOffset(NamePieces[0].size())); + return; + } + MultipleRanges = llvm::make_unique<SourceRange[]>(Locations.size()); + RangeOrNumRanges.setBegin( + SourceLocation::getFromRawEncoding(Locations.size())); + for (const auto &Loc : llvm::enumerate(Locations)) { + MultipleRanges[Loc.index()] = SourceRange( + Loc.value(), + Loc.value().getLocWithOffset(NamePieces[Loc.index()].size())); + } +} diff --git a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp index 2769802ad2bc..40b70d8a0590 100644 --- a/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp +++ b/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -39,6 +39,21 @@ using namespace llvm; namespace clang { namespace tooling { +const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl) { + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + // FIXME: (Alex L): Canonicalize implicit template instantions, just like + // the indexer does it. + + // Note: please update the declaration's doc comment every time the + // canonicalization rules are changed. + return FoundDecl; +} + namespace { // \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to // AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given @@ -58,6 +73,7 @@ public: if (checkIfOverriddenFunctionAscends(OverriddenMethod)) USRSet.insert(getUSRForDecl(OverriddenMethod)); } + addUSRsOfInstantiatedMethods(MethodDecl); } else if (const auto *RecordDecl = dyn_cast<CXXRecordDecl>(FoundDecl)) { handleCXXRecordDecl(RecordDecl); } else if (const auto *TemplateDecl = @@ -69,9 +85,13 @@ public: return std::vector<std::string>(USRSet.begin(), USRSet.end()); } + bool shouldVisitTemplateInstantiations() const { return true; } + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { if (MethodDecl->isVirtual()) OverriddenMethods.push_back(MethodDecl); + if (MethodDecl->getInstantiatedFromMemberFunction()) + InstantiatedMethods.push_back(MethodDecl); return true; } @@ -122,6 +142,20 @@ private: addUSRsOfOverridenFunctions(OverriddenMethod); } + void addUSRsOfInstantiatedMethods(const CXXMethodDecl *MethodDecl) { + // For renaming a class template method, all references of the instantiated + // member methods should be renamed too, so add USRs of the instantiated + // methods to the USR set. + USRSet.insert(getUSRForDecl(MethodDecl)); + if (const auto *FT = MethodDecl->getInstantiatedFromMemberFunction()) + USRSet.insert(getUSRForDecl(FT)); + for (const auto *Method : InstantiatedMethods) { + if (USRSet.find(getUSRForDecl( + Method->getInstantiatedFromMemberFunction())) != USRSet.end()) + USRSet.insert(getUSRForDecl(Method)); + } + } + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) @@ -135,10 +169,17 @@ private: ASTContext &Context; std::set<std::string> USRSet; std::vector<const CXXMethodDecl *> OverriddenMethods; + std::vector<const CXXMethodDecl *> InstantiatedMethods; std::vector<const ClassTemplatePartialSpecializationDecl *> PartialSpecs; }; } // namespace +std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND, + ASTContext &Context) { + AdditionalUSRFinder Finder(ND, Context); + return Finder.Find(); +} + class NamedDeclFindingConsumer : public ASTConsumer { public: NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets, @@ -183,8 +224,11 @@ private: return false; } - if (Force) + if (Force) { + SpellingNames.push_back(std::string()); + USRList.push_back(std::vector<std::string>()); return true; + } unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); @@ -193,13 +237,7 @@ private: return false; } - // If FoundDecl is a constructor or destructor, we want to instead take - // the Decl of the corresponding class. - if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl)) - FoundDecl = CtorDecl->getParent(); - else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl)) - FoundDecl = DtorDecl->getParent(); - + FoundDecl = getCanonicalSymbolDeclaration(FoundDecl); SpellingNames.push_back(FoundDecl->getNameAsString()); AdditionalUSRFinder Finder(FoundDecl, Context); USRList.push_back(Finder.Find()); diff --git a/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp b/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp index dc21a94610cb..c77304a17332 100644 --- a/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ b/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -23,6 +23,7 @@ #include "clang/Lex/Lexer.h" #include "clang/Tooling/Core/Lookup.h" #include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" #include "clang/Tooling/Refactoring/Rename/USRFinder.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" @@ -38,6 +39,17 @@ namespace tooling { namespace { +// Returns true if the given Loc is valid for edit. We don't edit the +// SourceLocations that are valid or in temporary buffer. +bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) { + if (Loc.isInvalid()) + return false; + const clang::FullSourceLoc FullLoc(Loc, SM); + std::pair<clang::FileID, unsigned> FileIdAndOffset = + FullLoc.getSpellingLoc().getDecomposedLoc(); + return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr; +} + // \brief This visitor recursively searches for all instances of a USR in a // translation unit and stores them for later usage. class USRLocFindingASTVisitor @@ -68,11 +80,9 @@ public: // Non-visitors: - // \brief Returns a list of unique locations. Duplicate or overlapping - // locations are erroneous and should be reported! - const std::vector<clang::SourceLocation> &getLocationsFound() const { - return LocationsFound; - } + /// \brief Returns a set of unique symbol occurrences. Duplicate or + /// overlapping occurrences are erroneous and should be reported! + SymbolOccurrences takeOccurrences() { return std::move(Occurrences); } private: void checkAndAddLocation(SourceLocation Loc) { @@ -82,17 +92,18 @@ private: StringRef TokenName = Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), Context.getSourceManager(), Context.getLangOpts()); - size_t Offset = TokenName.find(PrevName); + size_t Offset = TokenName.find(PrevName.getNamePieces()[0]); // The token of the source location we find actually has the old // name. if (Offset != StringRef::npos) - LocationsFound.push_back(BeginLoc.getLocWithOffset(Offset)); + Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol, + BeginLoc.getLocWithOffset(Offset)); } const std::set<std::string> USRSet; - const std::string PrevName; - std::vector<clang::SourceLocation> LocationsFound; + const SymbolName PrevName; + SymbolOccurrences Occurrences; const ASTContext &Context; }; @@ -160,13 +171,14 @@ public: const Decl *Context; // The nested name being replaced (can be nullptr). const NestedNameSpecifier *Specifier; + // Determine whether the prefix qualifiers of the NewName should be ignored. + // Normally, we set it to true for the symbol declaration and definition to + // avoid adding prefix qualifiers. + // For example, if it is true and NewName is "a::b::foo", then the symbol + // occurrence which the RenameInfo points to will be renamed to "foo". + bool IgnorePrefixQualifers; }; - // FIXME: Currently, prefix qualifiers will be added to the renamed symbol - // definition (e.g. "class Foo {};" => "class b::Bar {};" when renaming - // "a::Foo" to "b::Bar"). - // For renaming declarations/definitions, prefix qualifiers should be filtered - // out. bool VisitNamedDecl(const NamedDecl *Decl) { // UsingDecl has been handled in other place. if (llvm::isa<UsingDecl>(Decl)) @@ -180,19 +192,129 @@ public: return true; if (isInUSRSet(Decl)) { - RenameInfo Info = {Decl->getLocation(), Decl->getLocation(), nullptr, - nullptr, nullptr}; - RenameInfos.push_back(Info); + // For the case of renaming an alias template, we actually rename the + // underlying alias declaration of the template. + if (const auto* TAT = dyn_cast<TypeAliasTemplateDecl>(Decl)) + Decl = TAT->getTemplatedDecl(); + + auto StartLoc = Decl->getLocation(); + auto EndLoc = StartLoc; + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifers=*/true}; + RenameInfos.push_back(Info); + } } return true; } - bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + bool VisitMemberExpr(const MemberExpr *Expr) { const NamedDecl *Decl = Expr->getFoundDecl(); + auto StartLoc = Expr->getMemberLoc(); + auto EndLoc = Expr->getMemberLoc(); if (isInUSRSet(Decl)) { - RenameInfo Info = {Expr->getSourceRange().getBegin(), - Expr->getSourceRange().getEnd(), Decl, - getClosestAncestorDecl(*Expr), Expr->getQualifier()}; + RenameInfos.push_back({StartLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + return true; + } + + bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) { + // Fix the constructor initializer when renaming class members. + for (const auto *Initializer : CD->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + + if (const FieldDecl *FD = Initializer->getMember()) { + if (isInUSRSet(FD)) { + auto Loc = Initializer->getSourceLocation(); + RenameInfos.push_back({Loc, Loc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + } + } + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + // Get the underlying declaration of the shadow declaration introduced by a + // using declaration. + if (auto *UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) { + Decl = UsingShadow->getTargetDecl(); + } + + auto StartLoc = Expr->getLocStart(); + // For template function call expressions like `foo<int>()`, we want to + // restrict the end of location to just before the `<` character. + SourceLocation EndLoc = Expr->hasExplicitTemplateArgs() + ? Expr->getLAngleLoc().getLocWithOffset(-1) + : Expr->getLocEnd(); + + if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Decl)) { + if (isInUSRSet(MD)) { + // Handle renaming static template class methods, we only rename the + // name without prefix qualifiers and restrict the source range to the + // name. + RenameInfos.push_back({EndLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + return true; + } + } + + // In case of renaming an enum declaration, we have to explicitly handle + // unscoped enum constants referenced in expressions (e.g. + // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped + // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by + // TypeLoc. + if (const auto *T = llvm::dyn_cast<EnumConstantDecl>(Decl)) { + // FIXME: Handle the enum constant without prefix qualifiers (`a = Green`) + // when renaming an unscoped enum declaration with a new namespace. + if (!Expr->hasQualifier()) + return true; + + if (const auto *ED = + llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) { + if (ED->isScoped()) + return true; + Decl = ED; + } + // The current fix would qualify "ns1::ns2::Green" as + // "ns1::ns2::Color::Green". + // + // Get the EndLoc of the replacement by moving 1 character backward ( + // to exclude the last '::'). + // + // ns1::ns2::Green; + // ^ ^^ + // BeginLoc |EndLoc of the qualifier + // new EndLoc + EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1); + assert(EndLoc.isValid() && + "The enum constant should have prefix qualifers."); + } + if (isInUSRSet(Decl) && + IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + Decl, + getClosestAncestorDecl(*Expr), + Expr->getQualifier(), + /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } @@ -212,16 +334,16 @@ public: bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) { if (!NestedLoc.getNestedNameSpecifier()->getAsType()) return true; - if (IsTypeAliasWhichWillBeRenamedElsewhere(NestedLoc.getTypeLoc())) - return true; if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) { if (isInUSRSet(TargetDecl)) { RenameInfo Info = {NestedLoc.getBeginLoc(), EndLocationForType(NestedLoc.getTypeLoc()), - TargetDecl, getClosestAncestorDecl(NestedLoc), - NestedLoc.getNestedNameSpecifier()->getPrefix()}; + TargetDecl, + getClosestAncestorDecl(NestedLoc), + NestedLoc.getNestedNameSpecifier()->getPrefix(), + /*IgnorePrefixQualifers=*/false}; RenameInfos.push_back(Info); } } @@ -229,9 +351,6 @@ public: } bool VisitTypeLoc(TypeLoc Loc) { - if (IsTypeAliasWhichWillBeRenamedElsewhere(Loc)) - return true; - auto Parents = Context.getParents(Loc); TypeLoc ParentTypeLoc; if (!Parents.empty()) { @@ -265,10 +384,18 @@ public: if (!ParentTypeLoc.isNull() && isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc))) return true; - RenameInfo Info = {StartLocationForType(Loc), EndLocationForType(Loc), - TargetDecl, getClosestAncestorDecl(Loc), - GetNestedNameForType(Loc)}; - RenameInfos.push_back(Info); + + auto StartLoc = StartLocationForType(Loc); + auto EndLoc = EndLocationForType(Loc); + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + TargetDecl, + getClosestAncestorDecl(Loc), + GetNestedNameForType(Loc), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } return true; } } @@ -292,13 +419,20 @@ public: if (!ParentTypeLoc.isNull() && llvm::isa<ElaboratedType>(ParentTypeLoc.getType())) TargetLoc = ParentTypeLoc; - RenameInfo Info = { - StartLocationForType(TargetLoc), EndLocationForType(TargetLoc), - TemplateSpecType->getTemplateName().getAsTemplateDecl(), - getClosestAncestorDecl( - ast_type_traits::DynTypedNode::create(TargetLoc)), - GetNestedNameForType(TargetLoc)}; - RenameInfos.push_back(Info); + + auto StartLoc = StartLocationForType(TargetLoc); + auto EndLoc = EndLocationForType(TargetLoc); + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = { + StartLoc, + EndLoc, + TemplateSpecType->getTemplateName().getAsTemplateDecl(), + getClosestAncestorDecl( + ast_type_traits::DynTypedNode::create(TargetLoc)), + GetNestedNameForType(TargetLoc), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } } } return true; @@ -313,40 +447,16 @@ public: } private: - // FIXME: This method may not be suitable for renaming other types like alias - // types. Need to figure out a way to handle it. - bool IsTypeAliasWhichWillBeRenamedElsewhere(TypeLoc TL) const { - while (!TL.isNull()) { - // SubstTemplateTypeParm is the TypeLocation class for a substituted type - // inside a template expansion so we ignore these. For example: - // - // template<typename T> struct S { - // T t; // <-- this T becomes a TypeLoc(int) with class - // // SubstTemplateTypeParm when S<int> is instantiated - // } - if (TL.getTypeLocClass() == TypeLoc::SubstTemplateTypeParm) - return true; - - // Typedef is the TypeLocation class for a type which is a typedef to the - // type we want to replace. We ignore the use of the typedef as we will - // replace the definition of it. For example: - // - // typedef int T; - // T a; // <--- This T is a TypeLoc(int) with class Typedef. - if (TL.getTypeLocClass() == TypeLoc::Typedef) - return true; - TL = TL.getNextTypeLoc(); - } - return false; - } - // Get the supported declaration from a given typeLoc. If the declaration type // is not supported, returns nullptr. - // - // FIXME: support more types, e.g. enum, type alias. const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) { + if (const auto* TT = Loc.getType()->getAs<clang::TypedefType>()) + return TT->getDecl(); if (const auto *RD = Loc.getType()->getAsCXXRecordDecl()) return RD; + if (const auto *ED = + llvm::dyn_cast_or_null<EnumDecl>(Loc.getType()->getAsTagDecl())) + return ED; return nullptr; } @@ -391,12 +501,11 @@ private: } // namespace -std::vector<SourceLocation> -getLocationsOfUSRs(const std::vector<std::string> &USRs, StringRef PrevName, - Decl *Decl) { +SymbolOccurrences getOccurrencesOfUSRs(ArrayRef<std::string> USRs, + StringRef PrevName, Decl *Decl) { USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); Visitor.TraverseDecl(Decl); - return Visitor.getLocationsFound(); + return Visitor.takeOccurrences(); } std::vector<tooling::AtomicChange> @@ -424,18 +533,43 @@ createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, for (const auto &RenameInfo : Finder.getRenameInfos()) { std::string ReplacedName = NewName.str(); - if (RenameInfo.FromDecl && RenameInfo.Context) { - if (!llvm::isa<clang::TranslationUnitDecl>( - RenameInfo.Context->getDeclContext())) { - ReplacedName = tooling::replaceNestedName( - RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), - RenameInfo.FromDecl, - NewName.startswith("::") ? NewName.str() : ("::" + NewName).str()); + if (RenameInfo.IgnorePrefixQualifers) { + // Get the name without prefix qualifiers from NewName. + size_t LastColonPos = NewName.find_last_of(':'); + if (LastColonPos != std::string::npos) + ReplacedName = NewName.substr(LastColonPos + 1); + } else { + if (RenameInfo.FromDecl && RenameInfo.Context) { + if (!llvm::isa<clang::TranslationUnitDecl>( + RenameInfo.Context->getDeclContext())) { + ReplacedName = tooling::replaceNestedName( + RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), + RenameInfo.FromDecl, + NewName.startswith("::") ? NewName.str() + : ("::" + NewName).str()); + } else { + // This fixes the case where type `T` is a parameter inside a function + // type (e.g. `std::function<void(T)>`) and the DeclContext of `T` + // becomes the translation unit. As a workaround, we simply use + // fully-qualified name here for all references whose `DeclContext` is + // the translation unit and ignore the possible existence of + // using-decls (in the global scope) that can shorten the replaced + // name. + llvm::StringRef ActualName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + SourceRange(RenameInfo.Begin, RenameInfo.End)), + SM, TranslationUnitDecl->getASTContext().getLangOpts()); + // Add the leading "::" back if the name written in the code contains + // it. + if (ActualName.startswith("::") && !NewName.startswith("::")) { + ReplacedName = "::" + NewName.str(); + } + } } + // If the NewName contains leading "::", add it back. + if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) + ReplacedName = NewName.str(); } - // If the NewName contains leading "::", add it back. - if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) - ReplacedName = NewName.str(); Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName); } |