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 =
108  SM.isWrittenInMainFile(SM.getExpansionLoc(RenameDecl.getLocation()));
109 
110  // If the symbol is declared in the main file (which is not a header), we
111  // rename it.
112  if (DeclaredInMainFile && !MainFileIsHeader)
113  return None;
114 
115  // Below are cases where the symbol is declared in the header.
116  // If the symbol is function-local, we rename it.
117  if (RenameDecl.getParentFunctionOrMethod())
118  return None;
119 
120  if (!Index)
121  return ReasonToReject::NoIndexProvided;
122 
123  bool IsIndexable = isa<NamedDecl>(RenameDecl) &&
125  cast<NamedDecl>(RenameDecl), ASTCtx, {}, false);
126  // If the symbol is not indexable, we disallow rename.
127  if (!IsIndexable)
128  return ReasonToReject::NonIndexable;
129  auto OtherFile = getOtherRefFile(RenameDecl, MainFile, *Index);
130  // If the symbol is indexable and has no refs from other files in the index,
131  // we rename it.
132  if (!OtherFile)
133  return None;
134  // If the symbol is indexable and has refs from other files in the index,
135  // we disallow rename.
136  return ReasonToReject::UsedOutsideFile;
137 }
138 
140  auto Message = [](ReasonToReject Reason) {
141  switch (Reason) {
142  case NoIndexProvided:
143  return "symbol may be used in other files (no index available)";
144  case UsedOutsideFile:
145  return "the symbol is used outside main file";
146  case NonIndexable:
147  return "symbol may be used in other files (not eligible for indexing)";
148  case UnsupportedSymbol:
149  return "symbol is not a supported kind (e.g. namespace, macro)";
150  }
151  llvm_unreachable("unhandled reason kind");
152  };
153  return llvm::make_error<llvm::StringError>(
154  llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
155  llvm::inconvertibleErrorCode());
156 }
157 
158 } // namespace
159 
160 llvm::Expected<tooling::Replacements>
161 renameWithinFile(ParsedAST &AST, llvm::StringRef File, Position Pos,
162  llvm::StringRef NewName, const SymbolIndex *Index) {
163  RefactoringResultCollector ResultCollector;
164  ASTContext &ASTCtx = AST.getASTContext();
165  SourceLocation SourceLocationBeg = clangd::getBeginningOfIdentifier(
166  AST, Pos, AST.getSourceManager().getMainFileID());
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  tooling::RefactoringRuleContext Context(AST.getSourceManager());
172  Context.setASTContext(ASTCtx);
173  auto Rename = clang::tooling::RenameOccurrences::initiate(
174  Context, SourceRange(SourceLocationBeg), NewName);
175  if (!Rename)
176  return expandDiagnostics(Rename.takeError(), ASTCtx.getDiagnostics());
177 
178  const auto *RenameDecl = Rename->getRenameDecl();
179  assert(RenameDecl && "symbol must be found at this point");
180  if (auto Reject =
181  renamableWithinFile(*RenameDecl->getCanonicalDecl(), File, Index))
182  return makeError(*Reject);
183 
184  Rename->invoke(ResultCollector, Context);
185 
186  assert(ResultCollector.Result.hasValue());
187  if (!ResultCollector.Result.getValue())
188  return expandDiagnostics(ResultCollector.Result->takeError(),
189  ASTCtx.getDiagnostics());
190 
191  tooling::Replacements FilteredChanges;
192  // Right now we only support renaming the main file, so we
193  // drop replacements not for the main file. In the future, we might
194  // also support rename with wider scope.
195  // Rename sometimes returns duplicate edits (which is a bug). A side-effect of
196  // adding them to a single Replacements object is these are deduplicated.
197  for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
198  for (const auto &Rep : Change.getReplacements()) {
199  if (Rep.getFilePath() == File)
200  cantFail(FilteredChanges.add(Rep));
201  }
202  }
203  return FilteredChanges;
204 }
205 
206 } // namespace clangd
207 } // 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:484
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
Definition: Index.h:85
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:478
SourceLocation getBeginningOfIdentifier(const ParsedAST &Unit, const Position &Pos, const FileID FID)
Get the beginning SourceLocation at a specified Pos.
Definition: ClangdUnit.cpp:660
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:161
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:751
const SymbolIndex * Index
Definition: Dexp.cpp:84