clang-tools  9.0.0
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 "index/SymbolCollector.h"
13 #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
14 #include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
15 
16 namespace clang {
17 namespace clangd {
18 namespace {
19 
20 class RefactoringResultCollector final
21  : public tooling::RefactoringResultConsumer {
22 public:
23  void handleError(llvm::Error Err) override {
24  assert(!Result.hasValue());
25  Result = std::move(Err);
26  }
27 
28  // Using the handle(SymbolOccurrences) from parent class.
29  using tooling::RefactoringResultConsumer::handle;
30 
31  void handle(tooling::AtomicChanges SourceReplacements) override {
32  assert(!Result.hasValue());
33  Result = std::move(SourceReplacements);
34  }
35 
36  llvm::Optional<llvm::Expected<tooling::AtomicChanges>> Result;
37 };
38 
39 // Expand a DiagnosticError to make it print-friendly (print the detailed
40 // message, rather than "clang diagnostic").
41 llvm::Error expandDiagnostics(llvm::Error Err, DiagnosticsEngine &DE) {
42  if (auto Diag = DiagnosticError::take(Err)) {
43  llvm::cantFail(std::move(Err));
44  SmallVector<char, 128> DiagMessage;
45  Diag->second.EmitToString(DE, DiagMessage);
46  return llvm::make_error<llvm::StringError>(DiagMessage,
47  llvm::inconvertibleErrorCode());
48  }
49  return Err;
50 }
51 
52 llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
53  llvm::StringRef HintFilePath) {
54  if (!Loc)
55  return None;
56  auto Uri = URI::parse(Loc.FileURI);
57  if (!Uri) {
58  elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError());
59  return None;
60  }
61  auto U = URIForFile::fromURI(*Uri, HintFilePath);
62  if (!U) {
63  elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError());
64  return None;
65  }
66  return U->file().str();
67 }
68 
69 // Query the index to find some other files where the Decl is referenced.
70 llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
71  const SymbolIndex &Index) {
72  RefsRequest Req;
73  // We limit the number of results, this is a correctness/performance
74  // tradeoff. We expect the number of symbol references in the current file
75  // is smaller than the limit.
76  Req.Limit = 100;
77  if (auto ID = getSymbolID(&D))
78  Req.IDs.insert(*ID);
79  llvm::Optional<std::string> OtherFile;
80  Index.refs(Req, [&](const Ref &R) {
81  if (OtherFile)
82  return;
83  if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
84  if (*RefFilePath != MainFile)
85  OtherFile = *RefFilePath;
86  }
87  });
88  return OtherFile;
89 }
90 
92  NoIndexProvided,
93  NonIndexable,
94  UsedOutsideFile,
95  UnsupportedSymbol,
96 };
97 
98 // Check the symbol Decl is renameable (per the index) within the file.
99 llvm::Optional<ReasonToReject> renamableWithinFile(const Decl &RenameDecl,
100  StringRef MainFile,
101  const SymbolIndex *Index) {
102  if (llvm::isa<NamespaceDecl>(&RenameDecl))
103  return ReasonToReject::UnsupportedSymbol;
104  auto &ASTCtx = RenameDecl.getASTContext();
105  const auto &SM = ASTCtx.getSourceManager();
106  bool MainFileIsHeader = ASTCtx.getLangOpts().IsHeaderFile;
107  bool DeclaredInMainFile = isInsideMainFile(RenameDecl.getBeginLoc(), SM);
108 
109  // If the symbol is declared in the main file (which is not a header), we
110  // rename it.
111  if (DeclaredInMainFile && !MainFileIsHeader)
112  return None;
113 
114  // Below are cases where the symbol is declared in the header.
115  // If the symbol is function-local, we rename it.
116  if (RenameDecl.getParentFunctionOrMethod())
117  return None;
118 
119  if (!Index)
120  return ReasonToReject::NoIndexProvided;
121 
122  bool IsIndexable = isa<NamedDecl>(RenameDecl) &&
124  cast<NamedDecl>(RenameDecl), ASTCtx, {}, false);
125  // If the symbol is not indexable, we disallow rename.
126  if (!IsIndexable)
127  return ReasonToReject::NonIndexable;
128  auto OtherFile = getOtherRefFile(RenameDecl, MainFile, *Index);
129  // If the symbol is indexable and has no refs from other files in the index,
130  // we rename it.
131  if (!OtherFile)
132  return None;
133  // If the symbol is indexable and has refs from other files in the index,
134  // we disallow rename.
135  return ReasonToReject::UsedOutsideFile;
136 }
137 
139  auto Message = [](ReasonToReject Reason) {
140  switch (Reason) {
141  case NoIndexProvided:
142  return "symbol may be used in other files (no index available)";
143  case UsedOutsideFile:
144  return "the symbol is used outside main file";
145  case NonIndexable:
146  return "symbol may be used in other files (not eligible for indexing)";
147  case UnsupportedSymbol:
148  return "symbol is not a supported kind (e.g. namespace, macro)";
149  }
150  llvm_unreachable("unhandled reason kind");
151  };
152  return llvm::make_error<llvm::StringError>(
153  llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
154  llvm::inconvertibleErrorCode());
155 }
156 
157 } // namespace
158 
159 llvm::Expected<tooling::Replacements>
160 renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos,
161  llvm::StringRef NewName, const SymbolIndex *Index) {
162  RefactoringResultCollector ResultCollector;
163  ASTContext &ASTCtx = AST.getASTContext();
164  SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier(
165  AST, Pos, AST.getSourceManager().getMainFileID());
166  // FIXME: renaming macros is not supported yet, the macro-handling code should
167  // be moved to rename tooling library.
168  if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor()))
169  return makeError(UnsupportedSymbol);
170  tooling::RefactoringRuleContext Context(AST.getSourceManager());
171  Context.setASTContext(ASTCtx);
172  auto Rename = clang::tooling::RenameOccurrences::initiate(
173  Context, SourceRange(SourceLocationBeg), NewName);
174  if (!Rename)
175  return expandDiagnostics(Rename.takeError(), ASTCtx.getDiagnostics());
176 
177  const auto *RenameDecl = Rename->getRenameDecl();
178  assert(RenameDecl && "symbol must be found at this point");
179  if (auto Reject =
180  renamableWithinFile(*RenameDecl->getCanonicalDecl(), File, Index))
181  return makeError(*Reject);
182 
183  Rename->invoke(ResultCollector, Context);
184 
185  assert(ResultCollector.Result.hasValue());
186  if (!ResultCollector.Result.getValue())
187  return expandDiagnostics(ResultCollector.Result->takeError(),
188  ASTCtx.getDiagnostics());
189 
190  tooling::Replacements FilteredChanges;
191  // Right now we only support renaming the main file, so we
192  // drop replacements not for the main file. In the future, we might
193  // also support rename with wider scope.
194  // Rename sometimes returns duplicate edits (which is a bug). A side-effect of
195  // adding them to a single Replacements object is these are deduplicated.
196  for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
197  for (const auto &Rep : Change.getReplacements()) {
198  if (Rep.getFilePath() == File)
199  cantFail(FilteredChanges.add(Rep));
200  }
201  }
202  return FilteredChanges;
203 }
204 
205 } // namespace clangd
206 } // 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:154
static llvm::Error makeError(const char *Msg)
Definition: RIFF.cpp:16
Preprocessor & getPreprocessor()
Definition: ClangdUnit.cpp:483
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:372
constexpr llvm::StringLiteral Message
Documents should not be synced at all.
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: ClangdUnit.cpp:477
SourceLocation getBeginningOfIdentifier(const ParsedAST &Unit, const Position &Pos, const FileID FID)
Get the beginning SourceLocation at a specified Pos.
Definition: ClangdUnit.cpp:659
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:160
static llvm::Expected< URIForFile > fromURI(const URI &U, llvm::StringRef HintPath)
Definition: Protocol.cpp:45
const Decl * D
Definition: XRefs.cpp:868
A context is an immutable container for per-request data that must be propagated through layers that ...
Definition: Context.h:69
Stores and provides access to parsed AST.
Definition: ClangdUnit.h:73
SourceManager & getSourceManager()
Definition: ClangdUnit.h:99
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++ -*-===//
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: Rename.cpp:36
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:796
const SymbolIndex * Index
Definition: Dexp.cpp:84