clang-tools  10.0.0svn
Rename.cpp
Go to the documentation of this file.
1 //===--- Rename.cpp - Symbol-rename refactorings -----------------*- C++-*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "refactor/Rename.h"
10 #include "AST.h"
11 #include "Logger.h"
12 #include "ParsedAST.h"
13 #include "SourceCode.h"
14 #include "index/SymbolCollector.h"
15 #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
16 #include "clang/Tooling/Refactoring/Rename/USRFinder.h"
17 #include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
18 #include "clang/Tooling/Refactoring/Rename/USRLocFinder.h"
19 
20 namespace clang {
21 namespace clangd {
22 namespace {
23 
24 llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
25  llvm::StringRef HintFilePath) {
26  if (!Loc)
27  return None;
28  auto Uri = URI::parse(Loc.FileURI);
29  if (!Uri) {
30  elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
31  return None;
32  }
33  auto U = URIForFile::fromURI(*Uri, HintFilePath);
34  if (!U) {
35  elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
36  return None;
37  }
38  return U->file().str();
39 }
40 
41 // Query the index to find some other files where the Decl is referenced.
42 llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
43  const SymbolIndex &Index) {
44  RefsRequest Req;
45  // We limit the number of results, this is a correctness/performance
46  // tradeoff. We expect the number of symbol references in the current file
47  // is smaller than the limit.
48  Req.Limit = 100;
49  if (auto ID = getSymbolID(&D))
50  Req.IDs.insert(*ID);
51  llvm::Optional<std::string> OtherFile;
52  Index.refs(Req, [&](const Ref &R) {
53  if (OtherFile)
54  return;
55  if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
56  if (*RefFilePath != MainFile)
57  OtherFile = *RefFilePath;
58  }
59  });
60  return OtherFile;
61 }
62 
64  NoSymbolFound,
65  NoIndexProvided,
66  NonIndexable,
67  UsedOutsideFile,
68  UnsupportedSymbol,
69 };
70 
71 // Check the symbol Decl is renameable (per the index) within the file.
72 llvm::Optional<ReasonToReject> renamableWithinFile(const Decl &RenameDecl,
73  StringRef MainFile,
74  const SymbolIndex *Index) {
75  if (llvm::isa<NamespaceDecl>(&RenameDecl))
76  return ReasonToReject::UnsupportedSymbol;
77  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
78  if (FD->isOverloadedOperator())
79  return ReasonToReject::UnsupportedSymbol;
80  }
81  auto &ASTCtx = RenameDecl.getASTContext();
82  const auto &SM = ASTCtx.getSourceManager();
83  bool MainFileIsHeader = ASTCtx.getLangOpts().IsHeaderFile;
84  bool DeclaredInMainFile = isInsideMainFile(RenameDecl.getBeginLoc(), SM);
85 
86  if (!DeclaredInMainFile)
87  // We are sure the symbol is used externally, bail out early.
88  return UsedOutsideFile;
89 
90  // If the symbol is declared in the main file (which is not a header), we
91  // rename it.
92  if (!MainFileIsHeader)
93  return None;
94 
95  // Below are cases where the symbol is declared in the header.
96  // If the symbol is function-local, we rename it.
97  if (RenameDecl.getParentFunctionOrMethod())
98  return None;
99 
100  if (!Index)
101  return ReasonToReject::NoIndexProvided;
102 
103  bool IsIndexable = isa<NamedDecl>(RenameDecl) &&
105  cast<NamedDecl>(RenameDecl), ASTCtx, {}, false);
106  // If the symbol is not indexable, we disallow rename.
107  if (!IsIndexable)
108  return ReasonToReject::NonIndexable;
109  auto OtherFile = getOtherRefFile(RenameDecl, MainFile, *Index);
110  // If the symbol is indexable and has no refs from other files in the index,
111  // we rename it.
112  if (!OtherFile)
113  return None;
114  // If the symbol is indexable and has refs from other files in the index,
115  // we disallow rename.
116  return ReasonToReject::UsedOutsideFile;
117 }
118 
120  auto Message = [](ReasonToReject Reason) {
121  switch (Reason) {
122  case NoSymbolFound:
123  return "there is no symbol at the given location";
124  case NoIndexProvided:
125  return "symbol may be used in other files (no index available)";
126  case UsedOutsideFile:
127  return "the symbol is used outside main file";
128  case NonIndexable:
129  return "symbol may be used in other files (not eligible for indexing)";
130  case UnsupportedSymbol:
131  return "symbol is not a supported kind (e.g. namespace, macro)";
132  }
133  llvm_unreachable("unhandled reason kind");
134  };
135  return llvm::make_error<llvm::StringError>(
136  llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
137  llvm::inconvertibleErrorCode());
138 }
139 
140 // Return all rename occurrences in the main file.
141 tooling::SymbolOccurrences
142 findOccurrencesWithinFile(ParsedAST &AST, const NamedDecl *RenameDecl) {
143  const NamedDecl *CanonicalRenameDecl =
144  tooling::getCanonicalSymbolDeclaration(RenameDecl);
145  assert(CanonicalRenameDecl && "RenameDecl must be not null");
146  std::vector<std::string> RenameUSRs =
147  tooling::getUSRsForDeclaration(CanonicalRenameDecl, AST.getASTContext());
148  std::string OldName = CanonicalRenameDecl->getNameAsString();
149  tooling::SymbolOccurrences Result;
150  for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
151  tooling::SymbolOccurrences RenameInDecl =
152  tooling::getOccurrencesOfUSRs(RenameUSRs, OldName, TopLevelDecl);
153  Result.insert(Result.end(), std::make_move_iterator(RenameInDecl.begin()),
154  std::make_move_iterator(RenameInDecl.end()));
155  }
156  return Result;
157 }
158 
159 } // namespace
160 
161 llvm::Expected<tooling::Replacements>
162 renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos,
163  llvm::StringRef NewName, const SymbolIndex *Index) {
164  const SourceManager &SM = AST.getSourceManager();
165  SourceLocation SourceLocationBeg = SM.getMacroArgExpandedLocation(
166  getBeginningOfIdentifier(Pos, SM, AST.getASTContext().getLangOpts()));
167  // FIXME: renaming macros is not supported yet, the macro-handling code should
168  // be moved to rename tooling library.
169  if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor()))
170  return makeError(UnsupportedSymbol);
171 
172  const auto *RenameDecl =
173  tooling::getNamedDeclAt(AST.getASTContext(), SourceLocationBeg);
174  if (!RenameDecl)
175  return makeError(NoSymbolFound);
176 
177  if (auto Reject =
178  renamableWithinFile(*RenameDecl->getCanonicalDecl(), File, Index))
179  return makeError(*Reject);
180 
181  // Rename sometimes returns duplicate edits (which is a bug). A side-effect of
182  // adding them to a single Replacements object is these are deduplicated.
183  tooling::Replacements FilteredChanges;
184  for (const tooling::SymbolOccurrence &Rename :
185  findOccurrencesWithinFile(AST, RenameDecl)) {
186  // Currently, we only support normal rename (one range) for C/C++.
187  // FIXME: support multiple-range rename for objective-c methods.
188  if (Rename.getNameRanges().size() > 1)
189  continue;
190  // We shouldn't have conflicting replacements. If there are conflicts, it
191  // means that we have bugs either in clangd or in Rename library, therefore
192  // we refuse to perform the rename.
193  if (auto Err = FilteredChanges.add(tooling::Replacement(
194  AST.getASTContext().getSourceManager(),
195  CharSourceRange::getCharRange(Rename.getNameRanges()[0]), NewName)))
196  return std::move(Err);
197  }
198  return FilteredChanges;
199 }
200 
201 } // namespace clangd
202 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
llvm::Optional< SymbolID > getSymbolID(const Decl *D)
Gets the symbol ID for a declaration, if possible.
Definition: AST.cpp:199
static llvm::Error makeError(const char *Msg)
Definition: RIFF.cpp:16
Preprocessor & getPreprocessor()
Definition: ParsedAST.cpp:430
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
Definition: Index.h:85
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
Definition: SourceCode.cpp:537
SourceLocation getBeginningOfIdentifier(const Position &Pos, const SourceManager &SM, const LangOptions &LangOpts)
Get the beginning SourceLocation at a specified Pos in the main file.
Definition: SourceCode.cpp:279
constexpr llvm::StringLiteral Message
ArrayRef< Decl * > getLocalTopLevelDecls()
This function returns top-level decls present in the main file of the AST.
Definition: ParsedAST.cpp:440
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:56
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
Definition: ParsedAST.cpp:424
std::string MainFile
llvm::Expected< tooling::Replacements > renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos, llvm::StringRef NewName, const SymbolIndex *Index)
Renames all occurrences of the symbol at Pos to NewName.
Definition: Rename.cpp:162
static llvm::Expected< URIForFile > fromURI(const URI &U, llvm::StringRef HintPath)
Definition: Protocol.cpp:45
const Decl * D
Definition: XRefs.cpp:849
Stores and provides access to parsed AST.
Definition: ParsedAST.h:46
SourceManager & getSourceManager()
Definition: ParsedAST.h:73
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static llvm::Expected< URI > parse(llvm::StringRef Uri)
Parse a URI string "<scheme>:[//<authority>/]<path>".
Definition: URI.cpp:164
llvm::Optional< DefinedMacro > locateMacroAt(SourceLocation Loc, Preprocessor &PP)
Definition: SourceCode.cpp:965
const SymbolIndex * Index
Definition: Dexp.cpp:84