Index: include/clang/Tooling/Refactoring/Rename/RenamingAction.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/RenamingAction.h +++ include/clang/Tooling/Refactoring/Rename/RenamingAction.h @@ -16,6 +16,8 @@ #define LLVM_CLANG_TOOLING_REFACTOR_RENAME_RENAMING_ACTION_H #include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/Support/Error.h" namespace clang { class ASTConsumer; @@ -23,6 +25,8 @@ namespace tooling { +class SymbolOccurrences; + class RenamingAction { public: RenamingAction(const std::vector &NewNames, @@ -42,6 +46,13 @@ bool PrintLocations; }; +/// Returns source replacements that correspond to the rename of the given +/// symbol occurrences. +Expected> +createRenameReplacements(const SymbolOccurrences &Occurrences, + const SourceManager &SM, + ArrayRef NewNameStrings); + /// Rename all symbols identified by the given USRs. class QualifiedRenamingAction { public: Index: include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h =================================================================== --- /dev/null +++ include/clang/Tooling/Refactoring/Rename/SymbolOccurrences.h @@ -0,0 +1,148 @@ +//===--- SymbolOccurrences.h - 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_TOOLING_REFACTOR_RENAME_SYMBOL_OCCURRENCES_H +#define LLVM_CLANG_TOOLING_REFACTOR_RENAME_SYMBOL_OCCURRENCES_H + +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tooling { + +/// An occurrence of a symbol in the source. +/// +/// Occurrences can have difference kinds, that describe whether this occurrence +/// is an exact semantic match, or whether this is a weaker textual match that's +/// not guaranteed to represent the exact declaration. +/// +/// A single occurrence of a symbol can span more than one source range to +/// account for things like Objective-C selectors. As well as that, occurrences +/// for multi-string names might have just one location if they correspond to +/// a macro expansion. +class SymbolOccurrence { +public: + enum OccurrenceKind { + /// This occurrence is an exact match and can be renamed automatically. + /// + /// Note: + /// Symbol occurrences in macro arguments that expand to different + /// declarations get marked as exact matches, and thus the renaming engine + /// will rename them e.g.: + /// + /// \code + /// #define MACRO(x) x + ns::x + /// int foo(int var) { + /// return MACRO(var); // var is renamed automatically here when + /// // either var or ns::var is renamed. + /// }; + /// \endcode + /// + /// The user will have to fixup their code manually after performing such a + /// rename. + /// FIXME: The rename verifier should notify user about this issue. + MatchingSymbol + }; + + SymbolOccurrence(OccurrenceKind Kind, ArrayRef Locations, + ArrayRef Lengths) + : Kind(Kind), Locations(Locations), NameLengths(Lengths.data()) { + assert(Locations.size() == Lengths.size() && + "mismatching number of locations and lengths"); + } + + OccurrenceKind getKind() const { return Kind; } + + ArrayRef getNameLocations() const { return Locations; } + ArrayRef getNameLengths() const { + return llvm::makeArrayRef(NameLengths, Locations.size()); + } + +private: + OccurrenceKind Kind; + ArrayRef Locations; + const unsigned *NameLengths; +}; + +/// A set of source symbol occurrences for one symbol in a single translation +/// unit. +class SymbolOccurrences { +public: + /// Creates a set of empty symbol occurrences for the symbol of a given name. + SymbolOccurrences(StringRef SymbolName) { + // While empty symbol names are valid (Objective-C selectors can have empty + // name pieces), occurrences Objective-C selectors are created using an + // array of strings instead of just one string. + assert(!SymbolName.empty() && "Invalid symbol name!"); + NamePieceSize.push_back(SymbolName.size()); + } + + SymbolOccurrences(SymbolOccurrences &&) = default; + SymbolOccurrences &operator=(SymbolOccurrences &&) = default; + + void emplace_back(SymbolOccurrence::OccurrenceKind Kind, + ArrayRef Locations); + + size_t size() const { return Occurrences.size(); } + bool empty() const { return Occurrences.empty(); } + +private: + /// Iterates over the symbol occurrences. + class Iterator + : public llvm::iterator_facade_base { + public: + Iterator(const SymbolOccurrences *Owner, size_t Index) + : Owner(Owner), Index(Index) {} + + bool operator==(const Iterator &Other) const { + return Index == Other.Index; + } + + SymbolOccurrence operator*() const { + const OccurrenceData &Data = Owner->Occurrences[Index]; + ArrayRef Locs = + llvm::makeArrayRef(Owner->Locations) + .slice(Data.LocationsOffset, Data.NumLocations); + return SymbolOccurrence(Data.Kind, Locs, Owner->NamePieceSize); + } + + Iterator &operator++() { + ++Index; + return *this; + } + + private: + const SymbolOccurrences *Owner; + size_t Index; + }; + + typedef Iterator const_iterator; + +public: + const_iterator begin() const { return Iterator(this, 0); } + const_iterator end() const { return Iterator(this, Occurrences.size()); } + +private: + struct OccurrenceData { + SymbolOccurrence::OccurrenceKind Kind; + unsigned LocationsOffset, NumLocations; + }; + SmallVector NamePieceSize; + std::vector Occurrences; + std::vector Locations; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RENAME_SYMBOL_OCCURRENCES_H Index: include/clang/Tooling/Refactoring/Rename/USRLocFinder.h =================================================================== --- include/clang/Tooling/Refactoring/Rename/USRLocFinder.h +++ include/clang/Tooling/Refactoring/Rename/USRLocFinder.h @@ -19,6 +19,7 @@ #include "clang/AST/AST.h" #include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h" #include "llvm/ADT/StringRef.h" #include #include @@ -38,10 +39,13 @@ createRenameAtomicChanges(llvm::ArrayRef USRs, llvm::StringRef NewName, Decl *TranslationUnitDecl); -// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! -std::vector -getLocationsOfUSRs(const std::vector &USRs, - llvm::StringRef PrevName, Decl *Decl); +/// Finds the symbol occurrences for the symbol that's identified by the given +/// USR set. +/// +/// \return SymbolOccurrences that can be converted to AtomicChanges when +/// renaming. +SymbolOccurrences getOccurrencesOfUSRs(ArrayRef USRs, + StringRef PrevName, Decl *Decl); } // end namespace tooling } // end namespace clang Index: lib/Tooling/Refactoring/CMakeLists.txt =================================================================== --- lib/Tooling/Refactoring/CMakeLists.txt +++ lib/Tooling/Refactoring/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_library(clangToolingRefactor AtomicChange.cpp Rename/RenamingAction.cpp + Rename/SymbolOccurrences.cpp Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp Index: lib/Tooling/Refactoring/Rename/RenamingAction.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/RenamingAction.cpp +++ lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -32,6 +32,42 @@ namespace clang { namespace tooling { +Expected> +createRenameReplacements(const SymbolOccurrences &Occurrences, + const SourceManager &SM, + ArrayRef NewNameStrings) { + // FIXME: A true local rename can use just one AtomicChange. + std::vector Changes; + for (const auto &Occurrence : Occurrences) { + ArrayRef Locs = Occurrence.getNameLocations(); + assert(NewNameStrings.size() == Locs.size() && + "Mismatching number of locations and name pieces"); + AtomicChange Change(SM, Locs[0]); + for (size_t I = 0, E = Locs.size(); I != E; ++I) { + auto Error = Change.replace(SM, Locs[I], Occurrence.getNameLengths()[I], + NewNameStrings[I]); + if (Error) + return std::move(Error); + } + Changes.push_back(std::move(Change)); + } + return Changes; +} + +static void +applyChanges(ArrayRef AtomicChanges, + std::map &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( @@ -52,29 +88,28 @@ const std::string &PrevName, const std::vector &USRs) { const SourceManager &SourceMgr = Context.getSourceManager(); - std::vector RenamingCandidates; - std::vector 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.getNameLocations()[0], 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). + StringRef NewNameRef = NewName; + Expected> Change = + createRenameReplacements(Occurrences, SourceMgr, NewNameRef); + if (!Change) { + llvm::errs() << "Failed to create renaming replacements for '" << PrevName + << "'! " << llvm::toString(Change.takeError()) << "\n"; + return; + } + applyChanges(*Change, FileToReplaces); } private: @@ -103,15 +138,7 @@ // 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"; - } - } - } + applyChanges(AtomicChanges, FileToReplaces); } } Index: lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp =================================================================== --- /dev/null +++ lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp @@ -0,0 +1,22 @@ +//===--- 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/AtomicChange.h" + +using namespace clang; +using namespace tooling; + +void SymbolOccurrences::emplace_back(SymbolOccurrence::OccurrenceKind Kind, + ArrayRef Locations) { + unsigned Offset = this->Locations.size(); + Occurrences.push_back({Kind, Offset, (unsigned)Locations.size()}); + for (const auto &Loc : Locations) + this->Locations.push_back(Loc); +} Index: lib/Tooling/Refactoring/Rename/USRLocFinder.cpp =================================================================== --- lib/Tooling/Refactoring/Rename/USRLocFinder.cpp +++ lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -48,8 +48,8 @@ const ASTContext &Context) : RecursiveSymbolVisitor(Context.getSourceManager(), Context.getLangOpts()), - USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { - } + USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), + Occurrences(PrevName), Context(Context) {} bool visitSymbolOccurrence(const NamedDecl *ND, ArrayRef NameRanges) { @@ -68,11 +68,9 @@ // Non-visitors: - // \brief Returns a list of unique locations. Duplicate or overlapping - // locations are erroneous and should be reported! - const std::vector &getLocationsFound() const { - return LocationsFound; - } + /// \brief Returns a set of unique symbol occurrences. Duplicate or + /// overlapping occurrences are erroneous and should be reported! + SymbolOccurrences &getOccurrences() { return Occurrences; } private: void checkAndAddLocation(SourceLocation Loc) { @@ -87,12 +85,13 @@ // 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(SymbolOccurrence::MatchingSymbol, + BeginLoc.getLocWithOffset(Offset)); } const std::set USRSet; const std::string PrevName; - std::vector LocationsFound; + SymbolOccurrences Occurrences; const ASTContext &Context; }; @@ -391,12 +390,11 @@ } // namespace -std::vector -getLocationsOfUSRs(const std::vector &USRs, StringRef PrevName, - Decl *Decl) { +SymbolOccurrences getOccurrencesOfUSRs(ArrayRef USRs, + StringRef PrevName, Decl *Decl) { USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); Visitor.TraverseDecl(Decl); - return Visitor.getLocationsFound(); + return std::move(Visitor.getOccurrences()); } std::vector