blob: ec4849aa024e531b58ee0da720774c6fad151575 [file] [log] [blame]
Haojian Wu488c5092019-05-31 14:38:16 +00001//===--- 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
Sam McCallc0949122019-05-07 07:11:56 +00009#include "refactor/Rename.h"
Haojian Wu7276a442019-06-25 08:43:17 +000010#include "AST.h"
Haojian Wu7db12302019-11-19 10:10:43 +010011#include "FindTarget.h"
Haojian Wu7276a442019-06-25 08:43:17 +000012#include "Logger.h"
Sam McCall915f9782019-09-04 09:46:06 +000013#include "ParsedAST.h"
Haojian Wu7db12302019-11-19 10:10:43 +010014#include "Selection.h"
Sam McCall19cefc22019-09-03 15:34:47 +000015#include "SourceCode.h"
Haojian Wu7276a442019-06-25 08:43:17 +000016#include "index/SymbolCollector.h"
Haojian Wu7db12302019-11-19 10:10:43 +010017#include "clang/AST/DeclCXX.h"
18#include "clang/AST/DeclTemplate.h"
19#include "clang/Basic/SourceLocation.h"
Haojian Wu8b491732019-08-09 10:55:22 +000020#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
Haojian Wu852bafa2019-10-23 14:40:20 +020021#include "llvm/ADT/STLExtras.h"
22#include "llvm/Support/Error.h"
Haojian Wu88053162019-11-19 15:23:36 +010023#include "llvm/Support/FormatVariadic.h"
Sam McCallc0949122019-05-07 07:11:56 +000024
25namespace clang {
26namespace clangd {
27namespace {
28
Haojian Wu7276a442019-06-25 08:43:17 +000029llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
30 llvm::StringRef HintFilePath) {
31 if (!Loc)
32 return None;
Haojian Wuf821ab82019-10-29 10:49:27 +010033 auto Path = URI::resolve(Loc.FileURI, HintFilePath);
34 if (!Path) {
35 elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError());
Haojian Wu7276a442019-06-25 08:43:17 +000036 return None;
37 }
Haojian Wuf821ab82019-10-29 10:49:27 +010038
39 return *Path;
Haojian Wu7276a442019-06-25 08:43:17 +000040}
41
Haojian Wu7db12302019-11-19 10:10:43 +010042// Returns true if the given location is expanded from any macro body.
43bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) {
44 while (Loc.isMacroID()) {
45 if (SM.isMacroBodyExpansion(Loc))
46 return true;
47 Loc = SM.getImmediateMacroCallerLoc(Loc);
48 }
49
50 return false;
51}
52
Haojian Wu7276a442019-06-25 08:43:17 +000053// Query the index to find some other files where the Decl is referenced.
Haojian Wu93a825c2019-06-27 13:24:10 +000054llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
Haojian Wu7276a442019-06-25 08:43:17 +000055 const SymbolIndex &Index) {
56 RefsRequest Req;
57 // We limit the number of results, this is a correctness/performance
58 // tradeoff. We expect the number of symbol references in the current file
59 // is smaller than the limit.
60 Req.Limit = 100;
Haojian Wu852bafa2019-10-23 14:40:20 +020061 Req.IDs.insert(*getSymbolID(&D));
Haojian Wu7276a442019-06-25 08:43:17 +000062 llvm::Optional<std::string> OtherFile;
63 Index.refs(Req, [&](const Ref &R) {
64 if (OtherFile)
65 return;
66 if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
67 if (*RefFilePath != MainFile)
68 OtherFile = *RefFilePath;
69 }
70 });
71 return OtherFile;
72}
73
Haojian Wu7db12302019-11-19 10:10:43 +010074llvm::DenseSet<const Decl *> locateDeclAt(ParsedAST &AST,
75 SourceLocation TokenStartLoc) {
76 unsigned Offset =
77 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
78
79 SelectionTree Selection(AST.getASTContext(), AST.getTokens(), Offset);
80 const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
81 if (!SelectedNode)
82 return {};
83
84 // If the location points to a Decl, we check it is actually on the name
85 // range of the Decl. This would avoid allowing rename on unrelated tokens.
86 // ^class Foo {} // SelectionTree returns CXXRecordDecl,
87 // // we don't attempt to trigger rename on this position.
Kirill Bobyrev9091f062019-12-03 10:12:17 +010088 // FIXME: Make this work on destructors, e.g. "~F^oo()".
Haojian Wu7db12302019-11-19 10:10:43 +010089 if (const auto *D = SelectedNode->ASTNode.get<Decl>()) {
90 if (D->getLocation() != TokenStartLoc)
91 return {};
92 }
93
94 llvm::DenseSet<const Decl *> Result;
95 for (const auto *D :
96 targetDecl(SelectedNode->ASTNode,
97 DeclRelation::Alias | DeclRelation::TemplatePattern))
98 Result.insert(D);
99 return Result;
100}
101
Haojian Wu7276a442019-06-25 08:43:17 +0000102enum ReasonToReject {
Haojian Wu8b491732019-08-09 10:55:22 +0000103 NoSymbolFound,
Haojian Wu7276a442019-06-25 08:43:17 +0000104 NoIndexProvided,
105 NonIndexable,
Haojian Wu852bafa2019-10-23 14:40:20 +0200106 UsedOutsideFile, // for within-file rename only.
Haojian Wu442a1202019-06-26 08:10:26 +0000107 UnsupportedSymbol,
Haojian Wu7db12302019-11-19 10:10:43 +0100108 AmbiguousSymbol,
Haojian Wu7276a442019-06-25 08:43:17 +0000109};
110
Haojian Wu852bafa2019-10-23 14:40:20 +0200111llvm::Optional<ReasonToReject> renameable(const Decl &RenameDecl,
112 StringRef MainFilePath,
113 const SymbolIndex *Index,
114 bool CrossFile) {
115 // Filter out symbols that are unsupported in both rename modes.
Haojian Wu93a825c2019-06-27 13:24:10 +0000116 if (llvm::isa<NamespaceDecl>(&RenameDecl))
Haojian Wu442a1202019-06-26 08:10:26 +0000117 return ReasonToReject::UnsupportedSymbol;
Haojian Wuaf28bb62019-09-16 10:16:56 +0000118 if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
119 if (FD->isOverloadedOperator())
120 return ReasonToReject::UnsupportedSymbol;
121 }
Haojian Wu852bafa2019-10-23 14:40:20 +0200122 // function-local symbols is safe to rename.
Haojian Wu93a825c2019-06-27 13:24:10 +0000123 if (RenameDecl.getParentFunctionOrMethod())
Haojian Wu7276a442019-06-25 08:43:17 +0000124 return None;
125
Haojian Wu902dc6c2019-11-29 14:58:44 +0100126 // Check whether the symbol being rename is indexable.
127 auto &ASTCtx = RenameDecl.getASTContext();
128 bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
129 bool DeclaredInMainFile =
130 isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager());
131 bool IsMainFileOnly = true;
132 if (MainFileIsHeader)
133 // main file is a header, the symbol can't be main file only.
134 IsMainFileOnly = false;
135 else if (!DeclaredInMainFile)
136 IsMainFileOnly = false;
Haojian Wu852bafa2019-10-23 14:40:20 +0200137 bool IsIndexable =
138 isa<NamedDecl>(RenameDecl) &&
139 SymbolCollector::shouldCollectSymbol(
140 cast<NamedDecl>(RenameDecl), RenameDecl.getASTContext(),
Haojian Wu902dc6c2019-11-29 14:58:44 +0100141 SymbolCollector::Options(), IsMainFileOnly);
Haojian Wu852bafa2019-10-23 14:40:20 +0200142 if (!IsIndexable) // If the symbol is not indexable, we disallow rename.
143 return ReasonToReject::NonIndexable;
144
145 if (!CrossFile) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200146 if (!DeclaredInMainFile)
147 // We are sure the symbol is used externally, bail out early.
148 return ReasonToReject::UsedOutsideFile;
149
150 // If the symbol is declared in the main file (which is not a header), we
151 // rename it.
152 if (!MainFileIsHeader)
153 return None;
154
155 if (!Index)
156 return ReasonToReject::NoIndexProvided;
157
158 auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *Index);
159 // If the symbol is indexable and has no refs from other files in the index,
160 // we rename it.
161 if (!OtherFile)
162 return None;
163 // If the symbol is indexable and has refs from other files in the index,
164 // we disallow rename.
165 return ReasonToReject::UsedOutsideFile;
166 }
167
168 assert(CrossFile);
Haojian Wu7276a442019-06-25 08:43:17 +0000169 if (!Index)
170 return ReasonToReject::NoIndexProvided;
171
Haojian Wu852bafa2019-10-23 14:40:20 +0200172 // Blacklist symbols that are not supported yet in cross-file mode due to the
173 // limitations of our index.
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100174 // FIXME: Renaming templates requires to rename all related specializations,
175 // our index doesn't have this information.
Haojian Wu852bafa2019-10-23 14:40:20 +0200176 if (RenameDecl.getDescribedTemplate())
177 return ReasonToReject::UnsupportedSymbol;
178
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100179 // FIXME: Renaming virtual methods requires to rename all overridens in
180 // subclasses, our index doesn't have this information.
181 // Note: Within-file rename does support this through the AST.
Haojian Wu852bafa2019-10-23 14:40:20 +0200182 if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
183 if (S->isVirtual())
184 return ReasonToReject::UnsupportedSymbol;
185 }
186 return None;
Haojian Wu7276a442019-06-25 08:43:17 +0000187}
188
Haojian Wu9d34f452019-07-01 09:26:48 +0000189llvm::Error makeError(ReasonToReject Reason) {
190 auto Message = [](ReasonToReject Reason) {
191 switch (Reason) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200192 case ReasonToReject::NoSymbolFound:
Haojian Wu8b491732019-08-09 10:55:22 +0000193 return "there is no symbol at the given location";
Haojian Wu852bafa2019-10-23 14:40:20 +0200194 case ReasonToReject::NoIndexProvided:
Haojian Wu08cce032019-11-28 11:39:48 +0100195 return "no index provided";
Haojian Wu852bafa2019-10-23 14:40:20 +0200196 case ReasonToReject::UsedOutsideFile:
Haojian Wu9d34f452019-07-01 09:26:48 +0000197 return "the symbol is used outside main file";
Haojian Wu852bafa2019-10-23 14:40:20 +0200198 case ReasonToReject::NonIndexable:
Haojian Wu9d34f452019-07-01 09:26:48 +0000199 return "symbol may be used in other files (not eligible for indexing)";
Haojian Wu852bafa2019-10-23 14:40:20 +0200200 case ReasonToReject::UnsupportedSymbol:
Haojian Wu9d34f452019-07-01 09:26:48 +0000201 return "symbol is not a supported kind (e.g. namespace, macro)";
Haojian Wu7db12302019-11-19 10:10:43 +0100202 case AmbiguousSymbol:
203 return "there are multiple symbols at the given location";
Haojian Wu9d34f452019-07-01 09:26:48 +0000204 }
205 llvm_unreachable("unhandled reason kind");
206 };
207 return llvm::make_error<llvm::StringError>(
208 llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
209 llvm::inconvertibleErrorCode());
210}
211
Haojian Wu8b491732019-08-09 10:55:22 +0000212// Return all rename occurrences in the main file.
Haojian Wu7db12302019-11-19 10:10:43 +0100213std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
214 const NamedDecl &ND) {
215 // In theory, locateDeclAt should return the primary template. However, if the
216 // cursor is under the underlying CXXRecordDecl of the ClassTemplateDecl, ND
217 // will be the CXXRecordDecl, for this case, we need to get the primary
218 // template maunally.
219 const auto &RenameDecl =
220 ND.getDescribedTemplate() ? *ND.getDescribedTemplate() : ND;
221 // getUSRsForDeclaration will find other related symbols, e.g. virtual and its
222 // overriddens, primary template and all explicit specializations.
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100223 // FIXME: Get rid of the remaining tooling APIs.
Haojian Wu7db12302019-11-19 10:10:43 +0100224 std::vector<std::string> RenameUSRs = tooling::getUSRsForDeclaration(
225 tooling::getCanonicalSymbolDeclaration(&RenameDecl), AST.getASTContext());
226 llvm::DenseSet<SymbolID> TargetIDs;
227 for (auto &USR : RenameUSRs)
228 TargetIDs.insert(SymbolID(USR));
229
230 std::vector<SourceLocation> Results;
Haojian Wu8b491732019-08-09 10:55:22 +0000231 for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
Haojian Wu7db12302019-11-19 10:10:43 +0100232 findExplicitReferences(TopLevelDecl, [&](ReferenceLoc Ref) {
233 if (Ref.Targets.empty())
234 return;
235 for (const auto *Target : Ref.Targets) {
236 auto ID = getSymbolID(Target);
237 if (!ID || TargetIDs.find(*ID) == TargetIDs.end())
238 return;
239 }
240 Results.push_back(Ref.NameLoc);
241 });
Haojian Wu8b491732019-08-09 10:55:22 +0000242 }
Haojian Wu7db12302019-11-19 10:10:43 +0100243
244 return Results;
Haojian Wu8b491732019-08-09 10:55:22 +0000245}
246
Haojian Wu852bafa2019-10-23 14:40:20 +0200247// AST-based rename, it renames all occurrences in the main file.
Sam McCallc0949122019-05-07 07:11:56 +0000248llvm::Expected<tooling::Replacements>
Haojian Wu852bafa2019-10-23 14:40:20 +0200249renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
250 llvm::StringRef NewName) {
Sam McCallb2a984c02019-09-04 10:15:27 +0000251 const SourceManager &SM = AST.getSourceManager();
Haojian Wu7276a442019-06-25 08:43:17 +0000252
Haojian Wu8b491732019-08-09 10:55:22 +0000253 tooling::Replacements FilteredChanges;
Haojian Wu852bafa2019-10-23 14:40:20 +0200254 for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
Haojian Wu7db12302019-11-19 10:10:43 +0100255 SourceLocation RenameLoc = Loc;
256 // We don't rename in any macro bodies, but we allow rename the symbol
257 // spelled in a top-level macro argument in the main file.
258 if (RenameLoc.isMacroID()) {
259 if (isInMacroBody(SM, RenameLoc))
260 continue;
261 RenameLoc = SM.getSpellingLoc(Loc);
262 }
263 // Filter out locations not from main file.
264 // We traverse only main file decls, but locations could come from an
265 // non-preamble #include file e.g.
266 // void test() {
267 // int f^oo;
268 // #include "use_foo.inc"
269 // }
270 if (!isInsideMainFile(RenameLoc, SM))
Haojian Wu8b491732019-08-09 10:55:22 +0000271 continue;
Haojian Wu8b491732019-08-09 10:55:22 +0000272 if (auto Err = FilteredChanges.add(tooling::Replacement(
Haojian Wu7db12302019-11-19 10:10:43 +0100273 SM, CharSourceRange::getTokenRange(RenameLoc), NewName)))
Haojian Wu8b491732019-08-09 10:55:22 +0000274 return std::move(Err);
Sam McCallc0949122019-05-07 07:11:56 +0000275 }
276 return FilteredChanges;
277}
278
Haojian Wu852bafa2019-10-23 14:40:20 +0200279Range toRange(const SymbolLocation &L) {
280 Range R;
281 R.start.line = L.Start.line();
282 R.start.character = L.Start.column();
283 R.end.line = L.End.line();
284 R.end.character = L.End.column();
285 return R;
Bill Wendling936de1c2019-12-02 14:05:28 -0800286}
Haojian Wu852bafa2019-10-23 14:40:20 +0200287
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100288// Return all rename occurrences (using the index) outside of the main file,
Haojian Wu852bafa2019-10-23 14:40:20 +0200289// grouped by the absolute file path.
Haojian Wu3c3aca22019-11-28 12:47:32 +0100290llvm::Expected<llvm::StringMap<std::vector<Range>>>
Haojian Wu852bafa2019-10-23 14:40:20 +0200291findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
292 llvm::StringRef MainFile, const SymbolIndex &Index) {
293 RefsRequest RQuest;
294 RQuest.IDs.insert(*getSymbolID(&RenameDecl));
295
Haojian Wu3c3aca22019-11-28 12:47:32 +0100296 // Absolute file path => rename occurrences in that file.
Haojian Wu852bafa2019-10-23 14:40:20 +0200297 llvm::StringMap<std::vector<Range>> AffectedFiles;
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100298 // FIXME: Make the limit customizable.
Haojian Wu3c3aca22019-11-28 12:47:32 +0100299 static constexpr size_t MaxLimitFiles = 50;
300 bool HasMore = Index.refs(RQuest, [&](const Ref &R) {
301 if (AffectedFiles.size() > MaxLimitFiles)
302 return;
Haojian Wu852bafa2019-10-23 14:40:20 +0200303 if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
304 if (*RefFilePath != MainFile)
305 AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
306 }
307 });
Haojian Wu3c3aca22019-11-28 12:47:32 +0100308
309 if (AffectedFiles.size() > MaxLimitFiles)
310 return llvm::make_error<llvm::StringError>(
311 llvm::formatv("The number of affected files exceeds the max limit {0}",
312 MaxLimitFiles),
313 llvm::inconvertibleErrorCode());
314 if (HasMore) {
315 return llvm::make_error<llvm::StringError>(
316 llvm::formatv("The symbol {0} has too many occurrences",
317 RenameDecl.getQualifiedNameAsString()),
318 llvm::inconvertibleErrorCode());
319 }
320
Haojian Wu852bafa2019-10-23 14:40:20 +0200321 return AffectedFiles;
322}
323
Haojian Wu852bafa2019-10-23 14:40:20 +0200324// Index-based rename, it renames all occurrences outside of the main file.
325//
326// The cross-file rename is purely based on the index, as we don't want to
327// build all ASTs for affected files, which may cause a performance hit.
328// We choose to trade off some correctness for performance and scalability.
329//
330// Clangd builds a dynamic index for all opened files on top of the static
331// index of the whole codebase. Dynamic index is up-to-date (respects dirty
332// buffers) as long as clangd finishes processing opened files, while static
333// index (background index) is relatively stale. We choose the dirty buffers
334// as the file content we rename on, and fallback to file content on disk if
335// there is no dirty buffer.
336//
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100337// FIXME: Add range patching heuristics to detect staleness of the index, and
338// report to users.
339// FIXME: Our index may return implicit references, which are not eligible for
340// rename, we should filter out these references.
Haojian Wu852bafa2019-10-23 14:40:20 +0200341llvm::Expected<FileEdits> renameOutsideFile(
342 const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
343 llvm::StringRef NewName, const SymbolIndex &Index,
344 llvm::function_ref<llvm::Expected<std::string>(PathRef)> GetFileContent) {
345 auto AffectedFiles =
346 findOccurrencesOutsideFile(RenameDecl, MainFilePath, Index);
Haojian Wu3c3aca22019-11-28 12:47:32 +0100347 if (!AffectedFiles)
348 return AffectedFiles.takeError();
Haojian Wu852bafa2019-10-23 14:40:20 +0200349 FileEdits Results;
Haojian Wu3c3aca22019-11-28 12:47:32 +0100350 for (auto &FileAndOccurrences : *AffectedFiles) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200351 llvm::StringRef FilePath = FileAndOccurrences.first();
352
353 auto AffectedFileCode = GetFileContent(FilePath);
354 if (!AffectedFileCode) {
355 elog("Fail to read file content: {0}", AffectedFileCode.takeError());
356 continue;
357 }
Haojian Wu66ab9322019-11-28 16:48:49 +0100358 auto RenameEdit =
359 buildRenameEdit(FilePath, *AffectedFileCode,
360 std::move(FileAndOccurrences.second), NewName);
Haojian Wu88053162019-11-19 15:23:36 +0100361 if (!RenameEdit) {
362 return llvm::make_error<llvm::StringError>(
363 llvm::formatv("fail to build rename edit for file {0}: {1}", FilePath,
364 llvm::toString(RenameEdit.takeError())),
365 llvm::inconvertibleErrorCode());
366 }
Haojian Wu852bafa2019-10-23 14:40:20 +0200367 if (!RenameEdit->Replacements.empty())
368 Results.insert({FilePath, std::move(*RenameEdit)});
369 }
370 return Results;
371}
372
373} // namespace
374
375llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) {
376 ParsedAST &AST = RInputs.AST;
377 const SourceManager &SM = AST.getSourceManager();
378 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
379 auto GetFileContent = [&RInputs,
380 &SM](PathRef AbsPath) -> llvm::Expected<std::string> {
381 llvm::Optional<std::string> DirtyBuffer;
382 if (RInputs.GetDirtyBuffer &&
383 (DirtyBuffer = RInputs.GetDirtyBuffer(AbsPath)))
384 return std::move(*DirtyBuffer);
385
386 auto Content =
387 SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath);
388 if (!Content)
389 return llvm::createStringError(
390 llvm::inconvertibleErrorCode(),
391 llvm::formatv("Fail to open file {0}: {1}", AbsPath,
392 Content.getError().message()));
393 if (!*Content)
394 return llvm::createStringError(
395 llvm::inconvertibleErrorCode(),
396 llvm::formatv("Got no buffer for file {0}", AbsPath));
397
398 return (*Content)->getBuffer().str();
399 };
400 SourceLocation SourceLocationBeg =
401 SM.getMacroArgExpandedLocation(getBeginningOfIdentifier(
402 RInputs.Pos, SM, AST.getASTContext().getLangOpts()));
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100403 // FIXME: Renaming macros is not supported yet, the macro-handling code should
Haojian Wu852bafa2019-10-23 14:40:20 +0200404 // be moved to rename tooling library.
405 if (locateMacroAt(SourceLocationBeg, AST.getPreprocessor()))
406 return makeError(ReasonToReject::UnsupportedSymbol);
407
408 auto DeclsUnderCursor = locateDeclAt(AST, SourceLocationBeg);
409 if (DeclsUnderCursor.empty())
410 return makeError(ReasonToReject::NoSymbolFound);
411 if (DeclsUnderCursor.size() > 1)
412 return makeError(ReasonToReject::AmbiguousSymbol);
413
414 const auto *RenameDecl = llvm::dyn_cast<NamedDecl>(*DeclsUnderCursor.begin());
415 if (!RenameDecl)
416 return makeError(ReasonToReject::UnsupportedSymbol);
417
418 auto Reject =
419 renameable(*RenameDecl->getCanonicalDecl(), RInputs.MainFilePath,
420 RInputs.Index, RInputs.AllowCrossFile);
421 if (Reject)
422 return makeError(*Reject);
423
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100424 // We have two implementations of the rename:
Haojian Wu852bafa2019-10-23 14:40:20 +0200425 // - AST-based rename: used for renaming local symbols, e.g. variables
426 // defined in a function body;
427 // - index-based rename: used for renaming non-local symbols, and not
428 // feasible for local symbols (as by design our index don't index these
429 // symbols by design;
430 // To make cross-file rename work for local symbol, we use a hybrid solution:
431 // - run AST-based rename on the main file;
432 // - run index-based rename on other affected files;
433 auto MainFileRenameEdit = renameWithinFile(AST, *RenameDecl, RInputs.NewName);
434 if (!MainFileRenameEdit)
435 return MainFileRenameEdit.takeError();
436
437 if (!RInputs.AllowCrossFile) {
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100438 // Within-file rename: just return the main file results.
Haojian Wu852bafa2019-10-23 14:40:20 +0200439 return FileEdits(
440 {std::make_pair(RInputs.MainFilePath,
441 Edit{MainFileCode, std::move(*MainFileRenameEdit)})});
442 }
443
444 FileEdits Results;
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100445 // Renameable safely guards us that at this point we are renaming a local
446 // symbol if we don't have index.
Haojian Wu852bafa2019-10-23 14:40:20 +0200447 if (RInputs.Index) {
448 auto OtherFilesEdits =
449 renameOutsideFile(*RenameDecl, RInputs.MainFilePath, RInputs.NewName,
450 *RInputs.Index, GetFileContent);
451 if (!OtherFilesEdits)
452 return OtherFilesEdits.takeError();
453 Results = std::move(*OtherFilesEdits);
454 }
455 // Attach the rename edits for the main file.
456 Results.try_emplace(RInputs.MainFilePath, MainFileCode,
457 std::move(*MainFileRenameEdit));
458 return Results;
459}
460
Haojian Wu66ab9322019-11-28 16:48:49 +0100461llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
462 llvm::StringRef InitialCode,
Haojian Wu88053162019-11-19 15:23:36 +0100463 std::vector<Range> Occurrences,
464 llvm::StringRef NewName) {
465 llvm::sort(Occurrences);
466 // These two always correspond to the same position.
467 Position LastPos{0, 0};
468 size_t LastOffset = 0;
469
470 auto Offset = [&](const Position &P) -> llvm::Expected<size_t> {
471 assert(LastPos <= P && "malformed input");
472 Position Shifted = {
473 P.line - LastPos.line,
474 P.line > LastPos.line ? P.character : P.character - LastPos.character};
475 auto ShiftedOffset =
476 positionToOffset(InitialCode.substr(LastOffset), Shifted);
477 if (!ShiftedOffset)
478 return llvm::make_error<llvm::StringError>(
479 llvm::formatv("fail to convert the position {0} to offset ({1})", P,
480 llvm::toString(ShiftedOffset.takeError())),
481 llvm::inconvertibleErrorCode());
482 LastPos = P;
483 LastOffset += *ShiftedOffset;
484 return LastOffset;
485 };
486
487 std::vector<std::pair</*start*/ size_t, /*end*/ size_t>> OccurrencesOffsets;
488 for (const auto &R : Occurrences) {
489 auto StartOffset = Offset(R.start);
490 if (!StartOffset)
491 return StartOffset.takeError();
492 auto EndOffset = Offset(R.end);
493 if (!EndOffset)
494 return EndOffset.takeError();
495 OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
496 }
497
498 tooling::Replacements RenameEdit;
499 for (const auto &R : OccurrencesOffsets) {
500 auto ByteLength = R.second - R.first;
501 if (auto Err = RenameEdit.add(
Haojian Wu66ab9322019-11-28 16:48:49 +0100502 tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName)))
Haojian Wu88053162019-11-19 15:23:36 +0100503 return std::move(Err);
504 }
505 return Edit(InitialCode, std::move(RenameEdit));
506}
507
Sam McCallc0949122019-05-07 07:11:56 +0000508} // namespace clangd
509} // namespace clang