blob: 0585897947dfc61393689d8bf1eec712844a29ed [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 Wu74c97ca2020-02-11 12:37:09 +010016#include "Trace.h"
Haojian Wu7276a442019-06-25 08:43:17 +000017#include "index/SymbolCollector.h"
Haojian Wu7db12302019-11-19 10:10:43 +010018#include "clang/AST/DeclCXX.h"
19#include "clang/AST/DeclTemplate.h"
20#include "clang/Basic/SourceLocation.h"
Haojian Wu8b491732019-08-09 10:55:22 +000021#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
Kirill Bobyrevec618822019-12-12 13:10:59 +010022#include "clang/Tooling/Syntax/Tokens.h"
Haojian Wu891f8222019-12-09 17:00:51 +010023#include "llvm/ADT/None.h"
Haojian Wu852bafa2019-10-23 14:40:20 +020024#include "llvm/ADT/STLExtras.h"
Kirill Bobyrevec618822019-12-12 13:10:59 +010025#include "llvm/Support/Casting.h"
Haojian Wu852bafa2019-10-23 14:40:20 +020026#include "llvm/Support/Error.h"
Haojian Wu88053162019-11-19 15:23:36 +010027#include "llvm/Support/FormatVariadic.h"
Haojian Wu891f8222019-12-09 17:00:51 +010028#include <algorithm>
Sam McCallc0949122019-05-07 07:11:56 +000029
30namespace clang {
31namespace clangd {
32namespace {
33
Haojian Wu7276a442019-06-25 08:43:17 +000034llvm::Optional<std::string> filePath(const SymbolLocation &Loc,
35 llvm::StringRef HintFilePath) {
36 if (!Loc)
37 return None;
Haojian Wuf821ab82019-10-29 10:49:27 +010038 auto Path = URI::resolve(Loc.FileURI, HintFilePath);
39 if (!Path) {
40 elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError());
Haojian Wu7276a442019-06-25 08:43:17 +000041 return None;
42 }
Haojian Wuf821ab82019-10-29 10:49:27 +010043
44 return *Path;
Haojian Wu7276a442019-06-25 08:43:17 +000045}
46
Haojian Wu7db12302019-11-19 10:10:43 +010047// Returns true if the given location is expanded from any macro body.
48bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) {
49 while (Loc.isMacroID()) {
50 if (SM.isMacroBodyExpansion(Loc))
51 return true;
52 Loc = SM.getImmediateMacroCallerLoc(Loc);
53 }
54
55 return false;
56}
57
Haojian Wu7276a442019-06-25 08:43:17 +000058// Query the index to find some other files where the Decl is referenced.
Haojian Wu93a825c2019-06-27 13:24:10 +000059llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
Haojian Wu7276a442019-06-25 08:43:17 +000060 const SymbolIndex &Index) {
61 RefsRequest Req;
62 // We limit the number of results, this is a correctness/performance
63 // tradeoff. We expect the number of symbol references in the current file
64 // is smaller than the limit.
65 Req.Limit = 100;
Haojian Wu852bafa2019-10-23 14:40:20 +020066 Req.IDs.insert(*getSymbolID(&D));
Haojian Wu7276a442019-06-25 08:43:17 +000067 llvm::Optional<std::string> OtherFile;
68 Index.refs(Req, [&](const Ref &R) {
69 if (OtherFile)
70 return;
71 if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
72 if (*RefFilePath != MainFile)
73 OtherFile = *RefFilePath;
74 }
75 });
76 return OtherFile;
77}
78
Haojian Wu0d893fd2020-01-27 10:39:55 +010079llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
80 SourceLocation TokenStartLoc) {
Haojian Wu7db12302019-11-19 10:10:43 +010081 unsigned Offset =
82 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
83
Sam McCallbe6d07c2020-02-23 20:03:00 +010084 SelectionTree Selection = SelectionTree::createRight(
85 AST.getASTContext(), AST.getTokens(), Offset, Offset);
Haojian Wu7db12302019-11-19 10:10:43 +010086 const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
87 if (!SelectedNode)
88 return {};
89
Haojian Wu0d893fd2020-01-27 10:39:55 +010090 llvm::DenseSet<const NamedDecl *> Result;
Sam McCallf06f4392020-01-03 17:26:33 +010091 for (const NamedDecl *D :
Haojian Wu7db12302019-11-19 10:10:43 +010092 targetDecl(SelectedNode->ASTNode,
Kirill Bobyrevec618822019-12-12 13:10:59 +010093 DeclRelation::Alias | DeclRelation::TemplatePattern)) {
Kirill Bobyrevec618822019-12-12 13:10:59 +010094 // Get to CXXRecordDecl from constructor or destructor.
Sam McCallf06f4392020-01-03 17:26:33 +010095 D = tooling::getCanonicalSymbolDeclaration(D);
96 Result.insert(D);
Kirill Bobyrevec618822019-12-12 13:10:59 +010097 }
Haojian Wu7db12302019-11-19 10:10:43 +010098 return Result;
99}
100
Haojian Wud6da8a12020-02-05 12:31:11 +0100101// By default, we blacklist C++ standard symbols and protobuf symbols as rename
102// these symbols would change system/generated files which are unlikely to be
103// modified.
Haojian Wu0d893fd2020-01-27 10:39:55 +0100104bool isBlacklisted(const NamedDecl &RenameDecl) {
Haojian Wud6da8a12020-02-05 12:31:11 +0100105 if (isProtoFile(RenameDecl.getLocation(),
106 RenameDecl.getASTContext().getSourceManager()))
107 return true;
Haojian Wu0d893fd2020-01-27 10:39:55 +0100108 static const auto *StdSymbols = new llvm::DenseSet<llvm::StringRef>({
109#define SYMBOL(Name, NameSpace, Header) {#NameSpace #Name},
110#include "StdSymbolMap.inc"
111#undef SYMBOL
112 });
Haojian Wu02323a32020-02-26 15:22:56 +0100113 return StdSymbols->count(printQualifiedName(RenameDecl));
Haojian Wu0d893fd2020-01-27 10:39:55 +0100114}
115
Haojian Wu7276a442019-06-25 08:43:17 +0000116enum ReasonToReject {
Haojian Wu8b491732019-08-09 10:55:22 +0000117 NoSymbolFound,
Haojian Wu7276a442019-06-25 08:43:17 +0000118 NoIndexProvided,
119 NonIndexable,
Haojian Wu852bafa2019-10-23 14:40:20 +0200120 UsedOutsideFile, // for within-file rename only.
Haojian Wu442a1202019-06-26 08:10:26 +0000121 UnsupportedSymbol,
Haojian Wu7db12302019-11-19 10:10:43 +0100122 AmbiguousSymbol,
Haojian Wu7276a442019-06-25 08:43:17 +0000123};
124
Haojian Wu0d893fd2020-01-27 10:39:55 +0100125llvm::Optional<ReasonToReject> renameable(const NamedDecl &RenameDecl,
Haojian Wu852bafa2019-10-23 14:40:20 +0200126 StringRef MainFilePath,
127 const SymbolIndex *Index,
128 bool CrossFile) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100129 trace::Span Tracer("Renameable");
Haojian Wu852bafa2019-10-23 14:40:20 +0200130 // Filter out symbols that are unsupported in both rename modes.
Haojian Wu93a825c2019-06-27 13:24:10 +0000131 if (llvm::isa<NamespaceDecl>(&RenameDecl))
Haojian Wu442a1202019-06-26 08:10:26 +0000132 return ReasonToReject::UnsupportedSymbol;
Haojian Wuaf28bb62019-09-16 10:16:56 +0000133 if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
134 if (FD->isOverloadedOperator())
135 return ReasonToReject::UnsupportedSymbol;
136 }
Haojian Wu852bafa2019-10-23 14:40:20 +0200137 // function-local symbols is safe to rename.
Haojian Wu93a825c2019-06-27 13:24:10 +0000138 if (RenameDecl.getParentFunctionOrMethod())
Haojian Wu7276a442019-06-25 08:43:17 +0000139 return None;
140
Haojian Wu0d893fd2020-01-27 10:39:55 +0100141 if (isBlacklisted(RenameDecl))
142 return ReasonToReject::UnsupportedSymbol;
143
Haojian Wu902dc6c2019-11-29 14:58:44 +0100144 // Check whether the symbol being rename is indexable.
145 auto &ASTCtx = RenameDecl.getASTContext();
146 bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
147 bool DeclaredInMainFile =
148 isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager());
149 bool IsMainFileOnly = true;
150 if (MainFileIsHeader)
151 // main file is a header, the symbol can't be main file only.
152 IsMainFileOnly = false;
153 else if (!DeclaredInMainFile)
154 IsMainFileOnly = false;
Haojian Wu0d893fd2020-01-27 10:39:55 +0100155 // If the symbol is not indexable, we disallow rename.
156 if (!SymbolCollector::shouldCollectSymbol(
157 RenameDecl, RenameDecl.getASTContext(), SymbolCollector::Options(),
158 IsMainFileOnly))
Haojian Wu852bafa2019-10-23 14:40:20 +0200159 return ReasonToReject::NonIndexable;
160
161 if (!CrossFile) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200162 if (!DeclaredInMainFile)
163 // We are sure the symbol is used externally, bail out early.
164 return ReasonToReject::UsedOutsideFile;
165
166 // If the symbol is declared in the main file (which is not a header), we
167 // rename it.
168 if (!MainFileIsHeader)
169 return None;
170
171 if (!Index)
172 return ReasonToReject::NoIndexProvided;
173
174 auto OtherFile = getOtherRefFile(RenameDecl, MainFilePath, *Index);
175 // If the symbol is indexable and has no refs from other files in the index,
176 // we rename it.
177 if (!OtherFile)
178 return None;
179 // If the symbol is indexable and has refs from other files in the index,
180 // we disallow rename.
181 return ReasonToReject::UsedOutsideFile;
182 }
183
184 assert(CrossFile);
Haojian Wu7276a442019-06-25 08:43:17 +0000185 if (!Index)
186 return ReasonToReject::NoIndexProvided;
187
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100188 // FIXME: Renaming virtual methods requires to rename all overridens in
189 // subclasses, our index doesn't have this information.
190 // Note: Within-file rename does support this through the AST.
Haojian Wu852bafa2019-10-23 14:40:20 +0200191 if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
192 if (S->isVirtual())
193 return ReasonToReject::UnsupportedSymbol;
194 }
195 return None;
Haojian Wu7276a442019-06-25 08:43:17 +0000196}
197
Haojian Wu9d34f452019-07-01 09:26:48 +0000198llvm::Error makeError(ReasonToReject Reason) {
199 auto Message = [](ReasonToReject Reason) {
200 switch (Reason) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200201 case ReasonToReject::NoSymbolFound:
Haojian Wu8b491732019-08-09 10:55:22 +0000202 return "there is no symbol at the given location";
Haojian Wu852bafa2019-10-23 14:40:20 +0200203 case ReasonToReject::NoIndexProvided:
Haojian Wu08cce032019-11-28 11:39:48 +0100204 return "no index provided";
Haojian Wu852bafa2019-10-23 14:40:20 +0200205 case ReasonToReject::UsedOutsideFile:
Haojian Wu9d34f452019-07-01 09:26:48 +0000206 return "the symbol is used outside main file";
Haojian Wu852bafa2019-10-23 14:40:20 +0200207 case ReasonToReject::NonIndexable:
Haojian Wu9d34f452019-07-01 09:26:48 +0000208 return "symbol may be used in other files (not eligible for indexing)";
Haojian Wu852bafa2019-10-23 14:40:20 +0200209 case ReasonToReject::UnsupportedSymbol:
Haojian Wu9d34f452019-07-01 09:26:48 +0000210 return "symbol is not a supported kind (e.g. namespace, macro)";
Haojian Wu7db12302019-11-19 10:10:43 +0100211 case AmbiguousSymbol:
212 return "there are multiple symbols at the given location";
Haojian Wu9d34f452019-07-01 09:26:48 +0000213 }
214 llvm_unreachable("unhandled reason kind");
215 };
216 return llvm::make_error<llvm::StringError>(
217 llvm::formatv("Cannot rename symbol: {0}", Message(Reason)),
218 llvm::inconvertibleErrorCode());
219}
220
Haojian Wu8b491732019-08-09 10:55:22 +0000221// Return all rename occurrences in the main file.
Haojian Wu7db12302019-11-19 10:10:43 +0100222std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
223 const NamedDecl &ND) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100224 trace::Span Tracer("FindOccurrenceeWithinFile");
Kirill Bobyrevec618822019-12-12 13:10:59 +0100225 // If the cursor is at the underlying CXXRecordDecl of the
226 // ClassTemplateDecl, ND will be the CXXRecordDecl. In this case, we need to
Kazuaki Ishizakidd5571d2020-04-05 15:28:11 +0900227 // get the primary template manually.
Haojian Wu7db12302019-11-19 10:10:43 +0100228 // getUSRsForDeclaration will find other related symbols, e.g. virtual and its
229 // overriddens, primary template and all explicit specializations.
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100230 // FIXME: Get rid of the remaining tooling APIs.
Haojian Wu3de9a5d2020-01-20 14:14:52 +0100231 const auto *RenameDecl =
Kirill Bobyrevec618822019-12-12 13:10:59 +0100232 ND.getDescribedTemplate() ? ND.getDescribedTemplate() : &ND;
233 std::vector<std::string> RenameUSRs =
234 tooling::getUSRsForDeclaration(RenameDecl, AST.getASTContext());
Haojian Wu7db12302019-11-19 10:10:43 +0100235 llvm::DenseSet<SymbolID> TargetIDs;
236 for (auto &USR : RenameUSRs)
237 TargetIDs.insert(SymbolID(USR));
238
239 std::vector<SourceLocation> Results;
Haojian Wu8b491732019-08-09 10:55:22 +0000240 for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
Haojian Wu7db12302019-11-19 10:10:43 +0100241 findExplicitReferences(TopLevelDecl, [&](ReferenceLoc Ref) {
242 if (Ref.Targets.empty())
243 return;
244 for (const auto *Target : Ref.Targets) {
245 auto ID = getSymbolID(Target);
246 if (!ID || TargetIDs.find(*ID) == TargetIDs.end())
247 return;
248 }
249 Results.push_back(Ref.NameLoc);
250 });
Haojian Wu8b491732019-08-09 10:55:22 +0000251 }
Haojian Wu7db12302019-11-19 10:10:43 +0100252
253 return Results;
Haojian Wu8b491732019-08-09 10:55:22 +0000254}
255
Haojian Wu852bafa2019-10-23 14:40:20 +0200256// AST-based rename, it renames all occurrences in the main file.
Sam McCallc0949122019-05-07 07:11:56 +0000257llvm::Expected<tooling::Replacements>
Haojian Wu852bafa2019-10-23 14:40:20 +0200258renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
259 llvm::StringRef NewName) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100260 trace::Span Tracer("RenameWithinFile");
Sam McCallb2a984c02019-09-04 10:15:27 +0000261 const SourceManager &SM = AST.getSourceManager();
Haojian Wu7276a442019-06-25 08:43:17 +0000262
Haojian Wu8b491732019-08-09 10:55:22 +0000263 tooling::Replacements FilteredChanges;
Haojian Wu852bafa2019-10-23 14:40:20 +0200264 for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
Haojian Wu7db12302019-11-19 10:10:43 +0100265 SourceLocation RenameLoc = Loc;
266 // We don't rename in any macro bodies, but we allow rename the symbol
267 // spelled in a top-level macro argument in the main file.
268 if (RenameLoc.isMacroID()) {
269 if (isInMacroBody(SM, RenameLoc))
270 continue;
271 RenameLoc = SM.getSpellingLoc(Loc);
272 }
273 // Filter out locations not from main file.
274 // We traverse only main file decls, but locations could come from an
275 // non-preamble #include file e.g.
276 // void test() {
277 // int f^oo;
278 // #include "use_foo.inc"
279 // }
280 if (!isInsideMainFile(RenameLoc, SM))
Haojian Wu8b491732019-08-09 10:55:22 +0000281 continue;
Haojian Wu8b491732019-08-09 10:55:22 +0000282 if (auto Err = FilteredChanges.add(tooling::Replacement(
Haojian Wu7db12302019-11-19 10:10:43 +0100283 SM, CharSourceRange::getTokenRange(RenameLoc), NewName)))
Haojian Wu8b491732019-08-09 10:55:22 +0000284 return std::move(Err);
Sam McCallc0949122019-05-07 07:11:56 +0000285 }
286 return FilteredChanges;
287}
288
Haojian Wu852bafa2019-10-23 14:40:20 +0200289Range toRange(const SymbolLocation &L) {
290 Range R;
291 R.start.line = L.Start.line();
292 R.start.character = L.Start.column();
293 R.end.line = L.End.line();
294 R.end.character = L.End.column();
295 return R;
Bill Wendling936de1c2019-12-02 14:05:28 -0800296}
Haojian Wu852bafa2019-10-23 14:40:20 +0200297
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100298// Return all rename occurrences (using the index) outside of the main file,
Haojian Wu852bafa2019-10-23 14:40:20 +0200299// grouped by the absolute file path.
Haojian Wu3c3aca22019-11-28 12:47:32 +0100300llvm::Expected<llvm::StringMap<std::vector<Range>>>
Haojian Wu852bafa2019-10-23 14:40:20 +0200301findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100302 llvm::StringRef MainFile, const SymbolIndex &Index,
303 size_t MaxLimitFiles) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100304 trace::Span Tracer("FindOccurrencesOutsideFile");
Haojian Wu852bafa2019-10-23 14:40:20 +0200305 RefsRequest RQuest;
306 RQuest.IDs.insert(*getSymbolID(&RenameDecl));
307
Haojian Wu3c3aca22019-11-28 12:47:32 +0100308 // Absolute file path => rename occurrences in that file.
Haojian Wu852bafa2019-10-23 14:40:20 +0200309 llvm::StringMap<std::vector<Range>> AffectedFiles;
Haojian Wu3c3aca22019-11-28 12:47:32 +0100310 bool HasMore = Index.refs(RQuest, [&](const Ref &R) {
Haojian Wud83ade42020-03-11 16:07:44 +0100311 if (AffectedFiles.size() >= MaxLimitFiles)
Haojian Wu3c3aca22019-11-28 12:47:32 +0100312 return;
Kirill Bobyrev10540e42020-02-06 10:28:47 +0100313 if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
314 return;
Haojian Wu852bafa2019-10-23 14:40:20 +0200315 if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
316 if (*RefFilePath != MainFile)
317 AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
318 }
319 });
Haojian Wu3c3aca22019-11-28 12:47:32 +0100320
Haojian Wud83ade42020-03-11 16:07:44 +0100321 if (AffectedFiles.size() >= MaxLimitFiles)
Haojian Wu3c3aca22019-11-28 12:47:32 +0100322 return llvm::make_error<llvm::StringError>(
323 llvm::formatv("The number of affected files exceeds the max limit {0}",
324 MaxLimitFiles),
325 llvm::inconvertibleErrorCode());
326 if (HasMore) {
327 return llvm::make_error<llvm::StringError>(
328 llvm::formatv("The symbol {0} has too many occurrences",
329 RenameDecl.getQualifiedNameAsString()),
330 llvm::inconvertibleErrorCode());
331 }
Haojian Wuf0004aa2019-12-10 22:15:29 +0100332 // Sort and deduplicate the results, in case that index returns duplications.
333 for (auto &FileAndOccurrences : AffectedFiles) {
334 auto &Ranges = FileAndOccurrences.getValue();
335 llvm::sort(Ranges);
336 Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
Haojian Wu74c97ca2020-02-11 12:37:09 +0100337
338 SPAN_ATTACH(Tracer, FileAndOccurrences.first(),
339 static_cast<int64_t>(Ranges.size()));
Haojian Wuf0004aa2019-12-10 22:15:29 +0100340 }
Haojian Wu852bafa2019-10-23 14:40:20 +0200341 return AffectedFiles;
342}
343
Haojian Wu852bafa2019-10-23 14:40:20 +0200344// Index-based rename, it renames all occurrences outside of the main file.
345//
346// The cross-file rename is purely based on the index, as we don't want to
347// build all ASTs for affected files, which may cause a performance hit.
348// We choose to trade off some correctness for performance and scalability.
349//
350// Clangd builds a dynamic index for all opened files on top of the static
351// index of the whole codebase. Dynamic index is up-to-date (respects dirty
352// buffers) as long as clangd finishes processing opened files, while static
353// index (background index) is relatively stale. We choose the dirty buffers
354// as the file content we rename on, and fallback to file content on disk if
355// there is no dirty buffer.
Haojian Wu852bafa2019-10-23 14:40:20 +0200356llvm::Expected<FileEdits> renameOutsideFile(
357 const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100358 llvm::StringRef NewName, const SymbolIndex &Index, size_t MaxLimitFiles,
Haojian Wu852bafa2019-10-23 14:40:20 +0200359 llvm::function_ref<llvm::Expected<std::string>(PathRef)> GetFileContent) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100360 trace::Span Tracer("RenameOutsideFile");
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100361 auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
362 Index, MaxLimitFiles);
Haojian Wu3c3aca22019-11-28 12:47:32 +0100363 if (!AffectedFiles)
364 return AffectedFiles.takeError();
Haojian Wu852bafa2019-10-23 14:40:20 +0200365 FileEdits Results;
Haojian Wu3c3aca22019-11-28 12:47:32 +0100366 for (auto &FileAndOccurrences : *AffectedFiles) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200367 llvm::StringRef FilePath = FileAndOccurrences.first();
368
369 auto AffectedFileCode = GetFileContent(FilePath);
370 if (!AffectedFileCode) {
371 elog("Fail to read file content: {0}", AffectedFileCode.takeError());
372 continue;
373 }
Haojian Wu891f8222019-12-09 17:00:51 +0100374 auto RenameRanges =
375 adjustRenameRanges(*AffectedFileCode, RenameDecl.getNameAsString(),
376 std::move(FileAndOccurrences.second),
377 RenameDecl.getASTContext().getLangOpts());
378 if (!RenameRanges) {
Kirill Bobyrev3b9715c2019-12-16 10:33:56 +0100379 // Our heuristics fails to adjust rename ranges to the current state of
Haojian Wu891f8222019-12-09 17:00:51 +0100380 // the file, it is most likely the index is stale, so we give up the
381 // entire rename.
382 return llvm::make_error<llvm::StringError>(
383 llvm::formatv("Index results don't match the content of file {0} "
384 "(the index may be stale)",
385 FilePath),
386 llvm::inconvertibleErrorCode());
387 }
Haojian Wu66ab9322019-11-28 16:48:49 +0100388 auto RenameEdit =
Haojian Wu891f8222019-12-09 17:00:51 +0100389 buildRenameEdit(FilePath, *AffectedFileCode, *RenameRanges, NewName);
Haojian Wu88053162019-11-19 15:23:36 +0100390 if (!RenameEdit) {
391 return llvm::make_error<llvm::StringError>(
392 llvm::formatv("fail to build rename edit for file {0}: {1}", FilePath,
393 llvm::toString(RenameEdit.takeError())),
394 llvm::inconvertibleErrorCode());
395 }
Haojian Wu852bafa2019-10-23 14:40:20 +0200396 if (!RenameEdit->Replacements.empty())
397 Results.insert({FilePath, std::move(*RenameEdit)});
398 }
399 return Results;
400}
401
Kirill Bobyrev3b9715c2019-12-16 10:33:56 +0100402// A simple edit is either changing line or column, but not both.
Haojian Wu891f8222019-12-09 17:00:51 +0100403bool impliesSimpleEdit(const Position &LHS, const Position &RHS) {
404 return LHS.line == RHS.line || LHS.character == RHS.character;
405}
406
407// Performs a DFS to enumerate all possible near-miss matches.
408// It finds the locations where the indexed occurrences are now spelled in
409// Lexed occurrences, a near miss is defined as:
410// - a near miss maps all of the **name** occurrences from the index onto a
411// *subset* of lexed occurrences (we allow a single name refers to more
412// than one symbol)
413// - all indexed occurrences must be mapped, and Result must be distinct and
Kazuaki Ishizakidd5571d2020-04-05 15:28:11 +0900414// preserve order (only support detecting simple edits to ensure a
Haojian Wu891f8222019-12-09 17:00:51 +0100415// robust mapping)
416// - each indexed -> lexed occurrences mapping correspondence may change the
417// *line* or *column*, but not both (increases chance of a robust mapping)
418void findNearMiss(
419 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
420 ArrayRef<Range> LexedRest, int LexedIndex, int &Fuel,
421 llvm::function_ref<void(const std::vector<size_t> &)> MatchedCB) {
422 if (--Fuel < 0)
423 return;
424 if (IndexedRest.size() > LexedRest.size())
425 return;
426 if (IndexedRest.empty()) {
427 MatchedCB(PartialMatch);
428 return;
429 }
430 if (impliesSimpleEdit(IndexedRest.front().start, LexedRest.front().start)) {
431 PartialMatch.push_back(LexedIndex);
432 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
433 LexedIndex + 1, Fuel, MatchedCB);
434 PartialMatch.pop_back();
435 }
436 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
437 LexedIndex + 1, Fuel, MatchedCB);
438}
439
Haojian Wu852bafa2019-10-23 14:40:20 +0200440} // namespace
441
442llvm::Expected<FileEdits> rename(const RenameInputs &RInputs) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100443 trace::Span Tracer("Rename flow");
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100444 const auto &Opts = RInputs.Opts;
Haojian Wu852bafa2019-10-23 14:40:20 +0200445 ParsedAST &AST = RInputs.AST;
446 const SourceManager &SM = AST.getSourceManager();
447 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
448 auto GetFileContent = [&RInputs,
449 &SM](PathRef AbsPath) -> llvm::Expected<std::string> {
450 llvm::Optional<std::string> DirtyBuffer;
451 if (RInputs.GetDirtyBuffer &&
452 (DirtyBuffer = RInputs.GetDirtyBuffer(AbsPath)))
453 return std::move(*DirtyBuffer);
454
455 auto Content =
456 SM.getFileManager().getVirtualFileSystem().getBufferForFile(AbsPath);
457 if (!Content)
458 return llvm::createStringError(
459 llvm::inconvertibleErrorCode(),
460 llvm::formatv("Fail to open file {0}: {1}", AbsPath,
461 Content.getError().message()));
462 if (!*Content)
463 return llvm::createStringError(
464 llvm::inconvertibleErrorCode(),
465 llvm::formatv("Got no buffer for file {0}", AbsPath));
466
467 return (*Content)->getBuffer().str();
468 };
Kirill Bobyrevec618822019-12-12 13:10:59 +0100469 // Try to find the tokens adjacent to the cursor position.
470 auto Loc = sourceLocationInMainFile(SM, RInputs.Pos);
471 if (!Loc)
472 return Loc.takeError();
473 const syntax::Token *IdentifierToken =
474 spelledIdentifierTouching(*Loc, AST.getTokens());
475 // Renames should only triggered on identifiers.
476 if (!IdentifierToken)
477 return makeError(ReasonToReject::NoSymbolFound);
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100478 // FIXME: Renaming macros is not supported yet, the macro-handling code should
Haojian Wu852bafa2019-10-23 14:40:20 +0200479 // be moved to rename tooling library.
Kadir Cetinkaya3ae2fc72020-02-28 09:25:40 +0100480 if (locateMacroAt(*IdentifierToken, AST.getPreprocessor()))
Haojian Wu852bafa2019-10-23 14:40:20 +0200481 return makeError(ReasonToReject::UnsupportedSymbol);
482
Kirill Bobyrevec618822019-12-12 13:10:59 +0100483 auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location());
Haojian Wu852bafa2019-10-23 14:40:20 +0200484 if (DeclsUnderCursor.empty())
485 return makeError(ReasonToReject::NoSymbolFound);
486 if (DeclsUnderCursor.size() > 1)
487 return makeError(ReasonToReject::AmbiguousSymbol);
488
Haojian Wu0d893fd2020-01-27 10:39:55 +0100489 const auto &RenameDecl =
490 llvm::cast<NamedDecl>(*(*DeclsUnderCursor.begin())->getCanonicalDecl());
491 auto Reject = renameable(RenameDecl, RInputs.MainFilePath, RInputs.Index,
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100492 Opts.AllowCrossFile);
Haojian Wu852bafa2019-10-23 14:40:20 +0200493 if (Reject)
494 return makeError(*Reject);
495
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100496 // We have two implementations of the rename:
Haojian Wu852bafa2019-10-23 14:40:20 +0200497 // - AST-based rename: used for renaming local symbols, e.g. variables
498 // defined in a function body;
499 // - index-based rename: used for renaming non-local symbols, and not
500 // feasible for local symbols (as by design our index don't index these
501 // symbols by design;
502 // To make cross-file rename work for local symbol, we use a hybrid solution:
503 // - run AST-based rename on the main file;
504 // - run index-based rename on other affected files;
Haojian Wu0d893fd2020-01-27 10:39:55 +0100505 auto MainFileRenameEdit = renameWithinFile(AST, RenameDecl, RInputs.NewName);
Haojian Wu852bafa2019-10-23 14:40:20 +0200506 if (!MainFileRenameEdit)
507 return MainFileRenameEdit.takeError();
508
Haojian Wuaa324c52020-02-27 14:46:01 +0100509 // return the main file edit if this is a within-file rename or the symbol
510 // being renamed is function local.
511 if (!Opts.AllowCrossFile || RenameDecl.getParentFunctionOrMethod()) {
Haojian Wu852bafa2019-10-23 14:40:20 +0200512 return FileEdits(
513 {std::make_pair(RInputs.MainFilePath,
514 Edit{MainFileCode, std::move(*MainFileRenameEdit)})});
515 }
516
517 FileEdits Results;
Kirill Bobyrev9091f062019-12-03 10:12:17 +0100518 // Renameable safely guards us that at this point we are renaming a local
519 // symbol if we don't have index.
Haojian Wu852bafa2019-10-23 14:40:20 +0200520 if (RInputs.Index) {
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100521 auto OtherFilesEdits = renameOutsideFile(
522 RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index,
523 Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
Haojian Wud83ade42020-03-11 16:07:44 +0100524 : Opts.LimitFiles,
Haojian Wu34d0e1b2020-02-19 15:37:36 +0100525 GetFileContent);
Haojian Wu852bafa2019-10-23 14:40:20 +0200526 if (!OtherFilesEdits)
527 return OtherFilesEdits.takeError();
528 Results = std::move(*OtherFilesEdits);
529 }
530 // Attach the rename edits for the main file.
531 Results.try_emplace(RInputs.MainFilePath, MainFileCode,
532 std::move(*MainFileRenameEdit));
533 return Results;
534}
535
Haojian Wu66ab9322019-11-28 16:48:49 +0100536llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
537 llvm::StringRef InitialCode,
Haojian Wu88053162019-11-19 15:23:36 +0100538 std::vector<Range> Occurrences,
539 llvm::StringRef NewName) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100540 trace::Span Tracer("BuildRenameEdit");
541 SPAN_ATTACH(Tracer, "file_path", AbsFilePath);
542 SPAN_ATTACH(Tracer, "rename_occurrences",
543 static_cast<int64_t>(Occurrences.size()));
544
Haojian Wuf0004aa2019-12-10 22:15:29 +0100545 assert(std::is_sorted(Occurrences.begin(), Occurrences.end()));
546 assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
547 Occurrences.end() &&
548 "Occurrences must be unique");
549
Haojian Wu88053162019-11-19 15:23:36 +0100550 // These two always correspond to the same position.
551 Position LastPos{0, 0};
552 size_t LastOffset = 0;
553
554 auto Offset = [&](const Position &P) -> llvm::Expected<size_t> {
555 assert(LastPos <= P && "malformed input");
556 Position Shifted = {
557 P.line - LastPos.line,
558 P.line > LastPos.line ? P.character : P.character - LastPos.character};
559 auto ShiftedOffset =
560 positionToOffset(InitialCode.substr(LastOffset), Shifted);
561 if (!ShiftedOffset)
562 return llvm::make_error<llvm::StringError>(
563 llvm::formatv("fail to convert the position {0} to offset ({1})", P,
564 llvm::toString(ShiftedOffset.takeError())),
565 llvm::inconvertibleErrorCode());
566 LastPos = P;
567 LastOffset += *ShiftedOffset;
568 return LastOffset;
569 };
570
571 std::vector<std::pair</*start*/ size_t, /*end*/ size_t>> OccurrencesOffsets;
572 for (const auto &R : Occurrences) {
573 auto StartOffset = Offset(R.start);
574 if (!StartOffset)
575 return StartOffset.takeError();
576 auto EndOffset = Offset(R.end);
577 if (!EndOffset)
578 return EndOffset.takeError();
579 OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
580 }
581
582 tooling::Replacements RenameEdit;
583 for (const auto &R : OccurrencesOffsets) {
584 auto ByteLength = R.second - R.first;
585 if (auto Err = RenameEdit.add(
Haojian Wu66ab9322019-11-28 16:48:49 +0100586 tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName)))
Haojian Wu88053162019-11-19 15:23:36 +0100587 return std::move(Err);
588 }
589 return Edit(InitialCode, std::move(RenameEdit));
590}
591
Haojian Wu891f8222019-12-09 17:00:51 +0100592// Details:
593// - lex the draft code to get all rename candidates, this yields a superset
594// of candidates.
595// - apply range patching heuristics to generate "authoritative" occurrences,
596// cases we consider:
597// (a) index returns a subset of candidates, we use the indexed results.
598// - fully equal, we are sure the index is up-to-date
599// - proper subset, index is correct in most cases? there may be false
600// positives (e.g. candidates got appended), but rename is still safe
601// (b) index returns non-candidate results, we attempt to map the indexed
602// ranges onto candidates in a plausible way (e.g. guess that lines
603// were inserted). If such a "near miss" is found, the rename is still
604// possible
605llvm::Optional<std::vector<Range>>
606adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
607 std::vector<Range> Indexed, const LangOptions &LangOpts) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100608 trace::Span Tracer("AdjustRenameRanges");
Haojian Wu891f8222019-12-09 17:00:51 +0100609 assert(!Indexed.empty());
Haojian Wuf0004aa2019-12-10 22:15:29 +0100610 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
Haojian Wu891f8222019-12-09 17:00:51 +0100611 std::vector<Range> Lexed =
612 collectIdentifierRanges(Identifier, DraftCode, LangOpts);
Haojian Wu891f8222019-12-09 17:00:51 +0100613 llvm::sort(Lexed);
614 return getMappedRanges(Indexed, Lexed);
615}
616
617llvm::Optional<std::vector<Range>> getMappedRanges(ArrayRef<Range> Indexed,
618 ArrayRef<Range> Lexed) {
Haojian Wu74c97ca2020-02-11 12:37:09 +0100619 trace::Span Tracer("GetMappedRanges");
Haojian Wu891f8222019-12-09 17:00:51 +0100620 assert(!Indexed.empty());
621 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
622 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
623
624 if (Indexed.size() > Lexed.size()) {
625 vlog("The number of lexed occurrences is less than indexed occurrences");
Haojian Wu74c97ca2020-02-11 12:37:09 +0100626 SPAN_ATTACH(
627 Tracer, "error",
628 "The number of lexed occurrences is less than indexed occurrences");
Haojian Wu891f8222019-12-09 17:00:51 +0100629 return llvm::None;
630 }
631 // Fast check for the special subset case.
632 if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
633 return Indexed.vec();
634
635 std::vector<size_t> Best;
636 size_t BestCost = std::numeric_limits<size_t>::max();
637 bool HasMultiple = 0;
638 std::vector<size_t> ResultStorage;
639 int Fuel = 10000;
640 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
641 [&](const std::vector<size_t> &Matched) {
642 size_t MCost =
643 renameRangeAdjustmentCost(Indexed, Lexed, Matched);
644 if (MCost < BestCost) {
645 BestCost = MCost;
646 Best = std::move(Matched);
647 HasMultiple = false; // reset
648 return;
649 }
650 if (MCost == BestCost)
651 HasMultiple = true;
652 });
653 if (HasMultiple) {
654 vlog("The best near miss is not unique.");
Haojian Wu74c97ca2020-02-11 12:37:09 +0100655 SPAN_ATTACH(Tracer, "error", "The best near miss is not unique");
Haojian Wu891f8222019-12-09 17:00:51 +0100656 return llvm::None;
657 }
658 if (Best.empty()) {
659 vlog("Didn't find a near miss.");
Haojian Wu74c97ca2020-02-11 12:37:09 +0100660 SPAN_ATTACH(Tracer, "error", "Didn't find a near miss");
Haojian Wu891f8222019-12-09 17:00:51 +0100661 return llvm::None;
662 }
663 std::vector<Range> Mapped;
664 for (auto I : Best)
665 Mapped.push_back(Lexed[I]);
Haojian Wu74c97ca2020-02-11 12:37:09 +0100666 SPAN_ATTACH(Tracer, "mapped_ranges", static_cast<int64_t>(Mapped.size()));
Haojian Wu891f8222019-12-09 17:00:51 +0100667 return Mapped;
668}
669
670// The cost is the sum of the implied edit sizes between successive diffs, only
671// simple edits are considered:
672// - insert/remove a line (change line offset)
673// - insert/remove a character on an existing line (change column offset)
674//
675// Example I, total result is 1 + 1 = 2.
676// diff[0]: line + 1 <- insert a line before edit 0.
677// diff[1]: line + 1
678// diff[2]: line + 1
679// diff[3]: line + 2 <- insert a line before edits 2 and 3.
680//
681// Example II, total result is 1 + 1 + 1 = 3.
682// diff[0]: line + 1 <- insert a line before edit 0.
683// diff[1]: column + 1 <- remove a line between edits 0 and 1, and insert a
684// character on edit 1.
685size_t renameRangeAdjustmentCost(ArrayRef<Range> Indexed, ArrayRef<Range> Lexed,
686 ArrayRef<size_t> MappedIndex) {
687 assert(Indexed.size() == MappedIndex.size());
688 assert(std::is_sorted(Indexed.begin(), Indexed.end()));
689 assert(std::is_sorted(Lexed.begin(), Lexed.end()));
690
691 int LastLine = -1;
692 int LastDLine = 0, LastDColumn = 0;
693 int Cost = 0;
694 for (size_t I = 0; I < Indexed.size(); ++I) {
695 int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line;
696 int DColumn =
697 Indexed[I].start.character - Lexed[MappedIndex[I]].start.character;
698 int Line = Indexed[I].start.line;
699 if (Line != LastLine)
Kazuaki Ishizakib7ecf1c2020-01-04 10:28:41 -0500700 LastDColumn = 0; // column offsets don't carry cross lines.
Haojian Wu891f8222019-12-09 17:00:51 +0100701 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
702 std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn);
703 }
704 return Cost;
705}
706
Sam McCallc0949122019-05-07 07:11:56 +0000707} // namespace clangd
708} // namespace clang