aboutsummaryrefslogtreecommitdiff
path: root/lib/Tooling/Refactoring
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2017-12-18 20:11:37 +0000
committerDimitry Andric <dim@FreeBSD.org>2017-12-18 20:11:37 +0000
commit461a67fa15370a9ec88f8f8a240bf7c123bb2029 (patch)
tree6942083d7d56bba40ec790a453ca58ad3baf6832 /lib/Tooling/Refactoring
parent75c3240472ba6ac2669ee72ca67eb72d4e2851fc (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.cpp453
-rw-r--r--lib/Tooling/Refactoring/ASTSelectionRequirements.cpp48
-rw-r--r--lib/Tooling/Refactoring/AtomicChange.cpp188
-rw-r--r--lib/Tooling/Refactoring/CMakeLists.txt13
-rw-r--r--lib/Tooling/Refactoring/Extract/Extract.cpp199
-rw-r--r--lib/Tooling/Refactoring/Extract/SourceExtraction.cpp112
-rw-r--r--lib/Tooling/Refactoring/Extract/SourceExtraction.h52
-rw-r--r--lib/Tooling/Refactoring/RefactoringActions.cpp114
-rw-r--r--lib/Tooling/Refactoring/Rename/RenamingAction.cpp197
-rw-r--r--lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp37
-rw-r--r--lib/Tooling/Refactoring/Rename/USRFindingAction.cpp54
-rw-r--r--lib/Tooling/Refactoring/Rename/USRLocFinder.cpp298
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);
}