clang-tools 17.0.0git
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 "FindTarget.h"
12#include "ParsedAST.h"
13#include "Selection.h"
14#include "SourceCode.h"
16#include "support/Logger.h"
17#include "support/Trace.h"
18#include "clang/AST/ASTContext.h"
19#include "clang/AST/ASTTypeTraits.h"
20#include "clang/AST/Decl.h"
21#include "clang/AST/DeclCXX.h"
22#include "clang/AST/DeclTemplate.h"
23#include "clang/AST/ParentMapContext.h"
24#include "clang/AST/Stmt.h"
25#include "clang/Basic/CharInfo.h"
26#include "clang/Basic/LLVM.h"
27#include "clang/Basic/SourceLocation.h"
28#include "clang/Tooling/Syntax/Tokens.h"
29#include "llvm/ADT/STLExtras.h"
30#include "llvm/ADT/StringExtras.h"
31#include "llvm/Support/Casting.h"
32#include "llvm/Support/Error.h"
33#include "llvm/Support/FormatVariadic.h"
34#include "llvm/Support/JSON.h"
35#include <algorithm>
36#include <optional>
37
38namespace clang {
39namespace clangd {
40namespace {
41
42std::optional<std::string> filePath(const SymbolLocation &Loc,
43 llvm::StringRef HintFilePath) {
44 if (!Loc)
45 return std::nullopt;
46 auto Path = URI::resolve(Loc.FileURI, HintFilePath);
47 if (!Path) {
48 elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError());
49 return std::nullopt;
50 }
51
52 return *Path;
53}
54
55// Returns true if the given location is expanded from any macro body.
56bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) {
57 while (Loc.isMacroID()) {
58 if (SM.isMacroBodyExpansion(Loc))
59 return true;
60 Loc = SM.getImmediateMacroCallerLoc(Loc);
61 }
62
63 return false;
64}
65
66// Canonical declarations help simplify the process of renaming. Examples:
67// - Template's canonical decl is the templated declaration (i.e.
68// ClassTemplateDecl is canonicalized to its child CXXRecordDecl,
69// FunctionTemplateDecl - to child FunctionDecl)
70// - Given a constructor/destructor, canonical declaration is the parent
71// CXXRecordDecl because we want to rename both type name and its ctor/dtor.
72// - All specializations are canonicalized to the primary template. For example:
73//
74// template <typename T, int U>
75// bool Foo = true; (1)
76//
77// template <typename T>
78// bool Foo<T, 0> = true; (2)
79//
80// template <>
81// bool Foo<int, 0> = true; (3)
82//
83// Here, both partial (2) and full (3) specializations are canonicalized to (1)
84// which ensures all three of them are renamed.
85const NamedDecl *canonicalRenameDecl(const NamedDecl *D) {
86 if (const auto *VarTemplate = dyn_cast<VarTemplateSpecializationDecl>(D))
87 return canonicalRenameDecl(
88 VarTemplate->getSpecializedTemplate()->getTemplatedDecl());
89 if (const auto *Template = dyn_cast<TemplateDecl>(D))
90 if (const NamedDecl *TemplatedDecl = Template->getTemplatedDecl())
91 return canonicalRenameDecl(TemplatedDecl);
92 if (const auto *ClassTemplateSpecialization =
93 dyn_cast<ClassTemplateSpecializationDecl>(D))
94 return canonicalRenameDecl(
95 ClassTemplateSpecialization->getSpecializedTemplate()
96 ->getTemplatedDecl());
97 if (const auto *Method = dyn_cast<CXXMethodDecl>(D)) {
98 if (Method->getDeclKind() == Decl::Kind::CXXConstructor ||
99 Method->getDeclKind() == Decl::Kind::CXXDestructor)
100 return canonicalRenameDecl(Method->getParent());
101 if (const FunctionDecl *InstantiatedMethod =
102 Method->getInstantiatedFromMemberFunction())
103 return canonicalRenameDecl(InstantiatedMethod);
104 // FIXME(kirillbobyrev): For virtual methods with
105 // size_overridden_methods() > 1, this will not rename all functions it
106 // overrides, because this code assumes there is a single canonical
107 // declaration.
108 if (Method->isVirtual() && Method->size_overridden_methods())
109 return canonicalRenameDecl(*Method->overridden_methods().begin());
110 }
111 if (const auto *Function = dyn_cast<FunctionDecl>(D))
112 if (const FunctionTemplateDecl *Template = Function->getPrimaryTemplate())
113 return canonicalRenameDecl(Template);
114 if (const auto *Field = dyn_cast<FieldDecl>(D)) {
115 // This is a hacky way to do something like
116 // CXXMethodDecl::getInstantiatedFromMemberFunction for the field because
117 // Clang AST does not store relevant information about the field that is
118 // instantiated.
119 const auto *FieldParent =
120 dyn_cast_or_null<CXXRecordDecl>(Field->getParent());
121 if (!FieldParent)
122 return Field->getCanonicalDecl();
123 FieldParent = FieldParent->getTemplateInstantiationPattern();
124 // Field is not instantiation.
125 if (!FieldParent || Field->getParent() == FieldParent)
126 return Field->getCanonicalDecl();
127 for (const FieldDecl *Candidate : FieldParent->fields())
128 if (Field->getDeclName() == Candidate->getDeclName())
129 return Candidate->getCanonicalDecl();
130 elog("FieldParent should have field with the same name as Field.");
131 }
132 if (const auto *VD = dyn_cast<VarDecl>(D)) {
133 if (const VarDecl *OriginalVD = VD->getInstantiatedFromStaticDataMember())
134 return canonicalRenameDecl(OriginalVD);
135 }
136 if (const auto *UD = dyn_cast<UsingShadowDecl>(D)) {
137 if (const auto *TargetDecl = UD->getTargetDecl())
138 return canonicalRenameDecl(TargetDecl);
139 }
140 return dyn_cast<NamedDecl>(D->getCanonicalDecl());
141}
142
143llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
144 SourceLocation TokenStartLoc) {
145 unsigned Offset =
146 AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
147
148 SelectionTree Selection = SelectionTree::createRight(
149 AST.getASTContext(), AST.getTokens(), Offset, Offset);
150 const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
151 if (!SelectedNode)
152 return {};
153
154 llvm::DenseSet<const NamedDecl *> Result;
155 for (const NamedDecl *D :
156 targetDecl(SelectedNode->ASTNode,
158 AST.getHeuristicResolver())) {
159 Result.insert(canonicalRenameDecl(D));
160 }
161 return Result;
162}
163
164void filterRenameTargets(llvm::DenseSet<const NamedDecl *> &Decls) {
165 // For something like
166 // namespace ns { void foo(); }
167 // void bar() { using ns::f^oo; foo(); }
168 // locateDeclAt() will return a UsingDecl and foo's actual declaration.
169 // For renaming, we're only interested in foo's declaration, so drop the other
170 // one. There should never be more than one UsingDecl here, otherwise the
171 // rename would be ambiguos anyway.
172 auto UD = std::find_if(Decls.begin(), Decls.end(), [](const NamedDecl *D) {
173 return llvm::isa<UsingDecl>(D);
174 });
175 if (UD != Decls.end()) {
176 Decls.erase(UD);
177 }
178}
179
180// By default, we exclude symbols from system headers and protobuf symbols as
181// renaming these symbols would change system/generated files which are unlikely
182// to be good candidates for modification.
183bool isExcluded(const NamedDecl &RenameDecl) {
184 const auto &SM = RenameDecl.getASTContext().getSourceManager();
185 return SM.isInSystemHeader(RenameDecl.getLocation()) ||
186 isProtoFile(RenameDecl.getLocation(), SM);
187}
188
189enum class ReasonToReject {
190 NoSymbolFound,
191 NoIndexProvided,
192 NonIndexable,
193 UnsupportedSymbol,
194 AmbiguousSymbol,
195
196 // name validation. FIXME: reconcile with InvalidName
197 SameName,
198};
199
200std::optional<ReasonToReject> renameable(const NamedDecl &RenameDecl,
201 StringRef MainFilePath,
202 const SymbolIndex *Index,
203 const RenameOptions &Opts) {
204 trace::Span Tracer("Renameable");
205 if (!Opts.RenameVirtual) {
206 if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
207 if (S->isVirtual())
208 return ReasonToReject::UnsupportedSymbol;
209 }
210 }
211 // Filter out symbols that are unsupported in both rename modes.
212 if (llvm::isa<NamespaceDecl>(&RenameDecl))
213 return ReasonToReject::UnsupportedSymbol;
214 if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
215 if (FD->isOverloadedOperator())
216 return ReasonToReject::UnsupportedSymbol;
217 }
218 // function-local symbols is safe to rename.
219 if (RenameDecl.getParentFunctionOrMethod())
220 return std::nullopt;
221
222 if (isExcluded(RenameDecl))
223 return ReasonToReject::UnsupportedSymbol;
224
225 // Check whether the symbol being rename is indexable.
226 auto &ASTCtx = RenameDecl.getASTContext();
227 bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
228 bool DeclaredInMainFile =
229 isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager());
230 bool IsMainFileOnly = true;
231 if (MainFileIsHeader)
232 // main file is a header, the symbol can't be main file only.
233 IsMainFileOnly = false;
234 else if (!DeclaredInMainFile)
235 IsMainFileOnly = false;
236 // If the symbol is not indexable, we disallow rename.
238 RenameDecl, RenameDecl.getASTContext(), SymbolCollector::Options(),
239 IsMainFileOnly))
240 return ReasonToReject::NonIndexable;
241
242 return std::nullopt;
243}
244
245llvm::Error makeError(ReasonToReject Reason) {
246 auto Message = [](ReasonToReject Reason) {
247 switch (Reason) {
248 case ReasonToReject::NoSymbolFound:
249 return "there is no symbol at the given location";
250 case ReasonToReject::NoIndexProvided:
251 return "no index provided";
252 case ReasonToReject::NonIndexable:
253 return "symbol may be used in other files (not eligible for indexing)";
254 case ReasonToReject::UnsupportedSymbol:
255 return "symbol is not a supported kind (e.g. namespace, macro)";
256 case ReasonToReject::AmbiguousSymbol:
257 return "there are multiple symbols at the given location";
258 case ReasonToReject::SameName:
259 return "new name is the same as the old name";
260 }
261 llvm_unreachable("unhandled reason kind");
262 };
263 return error("Cannot rename symbol: {0}", Message(Reason));
264}
265
266// Return all rename occurrences in the main file.
267std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
268 const NamedDecl &ND) {
269 trace::Span Tracer("FindOccurrencesWithinFile");
270 assert(canonicalRenameDecl(&ND) == &ND &&
271 "ND should be already canonicalized.");
272
273 std::vector<SourceLocation> Results;
274 for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
276 TopLevelDecl,
277 [&](ReferenceLoc Ref) {
278 if (Ref.Targets.empty())
279 return;
280 for (const auto *Target : Ref.Targets) {
281 if (canonicalRenameDecl(Target) == &ND) {
282 Results.push_back(Ref.NameLoc);
283 return;
284 }
285 }
286 },
287 AST.getHeuristicResolver());
288 }
289
290 return Results;
291}
292
293// Detect name conflict with othter DeclStmts in the same enclosing scope.
294const NamedDecl *lookupSiblingWithinEnclosingScope(ASTContext &Ctx,
295 const NamedDecl &RenamedDecl,
296 StringRef NewName) {
297 // Store Parents list outside of GetSingleParent, so that returned pointer is
298 // not invalidated.
299 DynTypedNodeList Storage(DynTypedNode::create(RenamedDecl));
300 auto GetSingleParent = [&](const DynTypedNode &Node) -> const DynTypedNode * {
301 Storage = Ctx.getParents(Node);
302 return (Storage.size() == 1) ? Storage.begin() : nullptr;
303 };
304
305 // We need to get to the enclosing scope: NamedDecl's parent is typically
306 // DeclStmt (or FunctionProtoTypeLoc in case of function arguments), so
307 // enclosing scope would be the second order parent.
308 const auto *Parent = GetSingleParent(DynTypedNode::create(RenamedDecl));
309 if (!Parent || !(Parent->get<DeclStmt>() || Parent->get<TypeLoc>()))
310 return nullptr;
311 Parent = GetSingleParent(*Parent);
312
313 // The following helpers check corresponding AST nodes for variable
314 // declarations with the name collision.
315 auto CheckDeclStmt = [&](const DeclStmt *DS,
316 StringRef Name) -> const NamedDecl * {
317 if (!DS)
318 return nullptr;
319 for (const auto &Child : DS->getDeclGroup())
320 if (const auto *ND = dyn_cast<NamedDecl>(Child))
321 if (ND != &RenamedDecl && ND->getName() == Name)
322 return ND;
323 return nullptr;
324 };
325 auto CheckCompoundStmt = [&](const Stmt *S,
326 StringRef Name) -> const NamedDecl * {
327 if (const auto *CS = dyn_cast_or_null<CompoundStmt>(S))
328 for (const auto *Node : CS->children())
329 if (const auto *Result = CheckDeclStmt(dyn_cast<DeclStmt>(Node), Name))
330 return Result;
331 return nullptr;
332 };
333 auto CheckConditionVariable = [&](const auto *Scope,
334 StringRef Name) -> const NamedDecl * {
335 if (!Scope)
336 return nullptr;
337 return CheckDeclStmt(Scope->getConditionVariableDeclStmt(), Name);
338 };
339
340 // CompoundStmt is the most common enclosing scope for function-local symbols
341 // In the simplest case we just iterate through sibling DeclStmts and check
342 // for collisions.
343 if (const auto *EnclosingCS = Parent->get<CompoundStmt>()) {
344 if (const auto *Result = CheckCompoundStmt(EnclosingCS, NewName))
345 return Result;
346 const auto *ScopeParent = GetSingleParent(*Parent);
347 // CompoundStmt may be found within if/while/for. In these cases, rename can
348 // collide with the init-statement variable decalaration, they should be
349 // checked.
350 if (const auto *Result =
351 CheckConditionVariable(ScopeParent->get<IfStmt>(), NewName))
352 return Result;
353 if (const auto *Result =
354 CheckConditionVariable(ScopeParent->get<WhileStmt>(), NewName))
355 return Result;
356 if (const auto *For = ScopeParent->get<ForStmt>())
357 if (const auto *Result = CheckDeclStmt(
358 dyn_cast_or_null<DeclStmt>(For->getInit()), NewName))
359 return Result;
360 // Also check if there is a name collision with function arguments.
361 if (const auto *Function = ScopeParent->get<FunctionDecl>())
362 for (const auto *Parameter : Function->parameters())
363 if (Parameter->getName() == NewName)
364 return Parameter;
365 return nullptr;
366 }
367
368 // When renaming a variable within init-statement within if/while/for
369 // condition, also check the CompoundStmt in the body.
370 if (const auto *EnclosingIf = Parent->get<IfStmt>()) {
371 if (const auto *Result = CheckCompoundStmt(EnclosingIf->getElse(), NewName))
372 return Result;
373 return CheckCompoundStmt(EnclosingIf->getThen(), NewName);
374 }
375 if (const auto *EnclosingWhile = Parent->get<WhileStmt>())
376 return CheckCompoundStmt(EnclosingWhile->getBody(), NewName);
377 if (const auto *EnclosingFor = Parent->get<ForStmt>()) {
378 // Check for conflicts with other declarations within initialization
379 // statement.
380 if (const auto *Result = CheckDeclStmt(
381 dyn_cast_or_null<DeclStmt>(EnclosingFor->getInit()), NewName))
382 return Result;
383 return CheckCompoundStmt(EnclosingFor->getBody(), NewName);
384 }
385 if (const auto *EnclosingFunction = Parent->get<FunctionDecl>()) {
386 // Check for conflicts with other arguments.
387 for (const auto *Parameter : EnclosingFunction->parameters())
388 if (Parameter != &RenamedDecl && Parameter->getName() == NewName)
389 return Parameter;
390 // FIXME: We don't modify all references to function parameters when
391 // renaming from forward declaration now, so using a name colliding with
392 // something in the definition's body is a valid transformation.
393 if (!EnclosingFunction->doesThisDeclarationHaveABody())
394 return nullptr;
395 return CheckCompoundStmt(EnclosingFunction->getBody(), NewName);
396 }
397
398 return nullptr;
399}
400
401// Lookup the declarations (if any) with the given Name in the context of
402// RenameDecl.
403const NamedDecl *lookupSiblingsWithinContext(ASTContext &Ctx,
404 const NamedDecl &RenamedDecl,
405 llvm::StringRef NewName) {
406 const auto &II = Ctx.Idents.get(NewName);
407 DeclarationName LookupName(&II);
408 DeclContextLookupResult LookupResult;
409 const auto *DC = RenamedDecl.getDeclContext();
410 while (DC->isTransparentContext())
411 DC = DC->getParent();
412 switch (DC->getDeclKind()) {
413 // The enclosing DeclContext may not be the enclosing scope, it might have
414 // false positives and negatives, so we only choose "confident" DeclContexts
415 // that don't have any subscopes that are neither DeclContexts nor
416 // transparent.
417 //
418 // Notably, FunctionDecl is excluded -- because local variables are not scoped
419 // to the function, but rather to the CompoundStmt that is its body. Lookup
420 // will not find function-local variables.
421 case Decl::TranslationUnit:
422 case Decl::Namespace:
423 case Decl::Record:
424 case Decl::Enum:
425 case Decl::CXXRecord:
426 LookupResult = DC->lookup(LookupName);
427 break;
428 default:
429 break;
430 }
431 // Lookup may contain the RenameDecl itself, exclude it.
432 for (const auto *D : LookupResult)
433 if (D->getCanonicalDecl() != RenamedDecl.getCanonicalDecl())
434 return D;
435 return nullptr;
436}
437
438const NamedDecl *lookupSiblingWithName(ASTContext &Ctx,
439 const NamedDecl &RenamedDecl,
440 llvm::StringRef NewName) {
441 trace::Span Tracer("LookupSiblingWithName");
442 if (const auto *Result =
443 lookupSiblingsWithinContext(Ctx, RenamedDecl, NewName))
444 return Result;
445 return lookupSiblingWithinEnclosingScope(Ctx, RenamedDecl, NewName);
446}
447
448struct InvalidName {
449 enum Kind {
450 Keywords,
451 Conflict,
452 BadIdentifier,
453 };
455 std::string Details;
456};
457std::string toString(InvalidName::Kind K) {
458 switch (K) {
459 case InvalidName::Keywords:
460 return "Keywords";
461 case InvalidName::Conflict:
462 return "Conflict";
463 case InvalidName::BadIdentifier:
464 return "BadIdentifier";
465 }
466 llvm_unreachable("unhandled InvalidName kind");
467}
468
469llvm::Error makeError(InvalidName Reason) {
470 auto Message = [](const InvalidName &Reason) {
471 switch (Reason.K) {
472 case InvalidName::Keywords:
473 return llvm::formatv("the chosen name \"{0}\" is a keyword",
474 Reason.Details);
475 case InvalidName::Conflict:
476 return llvm::formatv("conflict with the symbol in {0}", Reason.Details);
477 case InvalidName::BadIdentifier:
478 return llvm::formatv("the chosen name \"{0}\" is not a valid identifier",
479 Reason.Details);
480 }
481 llvm_unreachable("unhandled InvalidName kind");
482 };
483 return error("invalid name: {0}", Message(Reason));
484}
485
486static bool mayBeValidIdentifier(llvm::StringRef Ident) {
487 assert(llvm::json::isUTF8(Ident));
488 if (Ident.empty())
489 return false;
490 // We don't check all the rules for non-ascii characters (most are allowed).
491 bool AllowDollar = true; // lenient
492 if (llvm::isASCII(Ident.front()) &&
493 !isAsciiIdentifierStart(Ident.front(), AllowDollar))
494 return false;
495 for (char C : Ident) {
496 if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
497 return false;
498 }
499 return true;
500}
501
502// Check if we can rename the given RenameDecl into NewName.
503// Return details if the rename would produce a conflict.
504std::optional<InvalidName> checkName(const NamedDecl &RenameDecl,
505 llvm::StringRef NewName) {
506 trace::Span Tracer("CheckName");
507 static constexpr trace::Metric InvalidNameMetric(
508 "rename_name_invalid", trace::Metric::Counter, "invalid_kind");
509 auto &ASTCtx = RenameDecl.getASTContext();
510 std::optional<InvalidName> Result;
511 if (isKeyword(NewName, ASTCtx.getLangOpts()))
512 Result = InvalidName{InvalidName::Keywords, NewName.str()};
513 else if (!mayBeValidIdentifier(NewName))
514 Result = InvalidName{InvalidName::BadIdentifier, NewName.str()};
515 else {
516 // Name conflict detection.
517 // Function conflicts are subtle (overloading), so ignore them.
518 if (RenameDecl.getKind() != Decl::Function) {
519 if (auto *Conflict = lookupSiblingWithName(ASTCtx, RenameDecl, NewName))
520 Result = InvalidName{
521 InvalidName::Conflict,
522 Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
523 }
524 }
525 if (Result)
526 InvalidNameMetric.record(1, toString(Result->K));
527 return Result;
528}
529
530// AST-based rename, it renames all occurrences in the main file.
531llvm::Expected<tooling::Replacements>
532renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
533 llvm::StringRef NewName) {
534 trace::Span Tracer("RenameWithinFile");
535 const SourceManager &SM = AST.getSourceManager();
536
537 tooling::Replacements FilteredChanges;
538 for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
539 SourceLocation RenameLoc = Loc;
540 // We don't rename in any macro bodies, but we allow rename the symbol
541 // spelled in a top-level macro argument in the main file.
542 if (RenameLoc.isMacroID()) {
543 if (isInMacroBody(SM, RenameLoc))
544 continue;
545 RenameLoc = SM.getSpellingLoc(Loc);
546 }
547 // Filter out locations not from main file.
548 // We traverse only main file decls, but locations could come from an
549 // non-preamble #include file e.g.
550 // void test() {
551 // int f^oo;
552 // #include "use_foo.inc"
553 // }
554 if (!isInsideMainFile(RenameLoc, SM))
555 continue;
556 if (auto Err = FilteredChanges.add(tooling::Replacement(
557 SM, CharSourceRange::getTokenRange(RenameLoc), NewName)))
558 return std::move(Err);
559 }
560 return FilteredChanges;
561}
562
563Range toRange(const SymbolLocation &L) {
564 Range R;
565 R.start.line = L.Start.line();
566 R.start.character = L.Start.column();
567 R.end.line = L.End.line();
568 R.end.character = L.End.column();
569 return R;
570}
571
572// Walk down from a virtual method to overriding methods, we rename them as a
573// group. Note that canonicalRenameDecl() ensures we're starting from the base
574// method.
575void insertTransitiveOverrides(SymbolID Base, llvm::DenseSet<SymbolID> &IDs,
576 const SymbolIndex &Index) {
577 RelationsRequest Req;
578 Req.Predicate = RelationKind::OverriddenBy;
579
580 llvm::DenseSet<SymbolID> Pending = {Base};
581 while (!Pending.empty()) {
582 Req.Subjects = std::move(Pending);
583 Pending.clear();
584
585 Index.relations(Req, [&](const SymbolID &, const Symbol &Override) {
586 if (IDs.insert(Override.ID).second)
587 Pending.insert(Override.ID);
588 });
589 }
590}
591
592// Return all rename occurrences (using the index) outside of the main file,
593// grouped by the absolute file path.
594llvm::Expected<llvm::StringMap<std::vector<Range>>>
595findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
596 llvm::StringRef MainFile, const SymbolIndex &Index,
597 size_t MaxLimitFiles) {
598 trace::Span Tracer("FindOccurrencesOutsideFile");
599 RefsRequest RQuest;
600 RQuest.IDs.insert(getSymbolID(&RenameDecl));
601
602 if (const auto *MethodDecl = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl))
603 if (MethodDecl->isVirtual())
604 insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index);
605
606 // Absolute file path => rename occurrences in that file.
607 llvm::StringMap<std::vector<Range>> AffectedFiles;
608 bool HasMore = Index.refs(RQuest, [&](const Ref &R) {
609 if (AffectedFiles.size() >= MaxLimitFiles)
610 return;
611 if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
612 return;
613 if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
614 if (!pathEqual(*RefFilePath, MainFile))
615 AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
616 }
617 });
618
619 if (AffectedFiles.size() >= MaxLimitFiles)
620 return error("The number of affected files exceeds the max limit {0}",
621 MaxLimitFiles);
622 if (HasMore)
623 return error("The symbol {0} has too many occurrences",
624 RenameDecl.getQualifiedNameAsString());
625 // Sort and deduplicate the results, in case that index returns duplications.
626 for (auto &FileAndOccurrences : AffectedFiles) {
627 auto &Ranges = FileAndOccurrences.getValue();
628 llvm::sort(Ranges);
629 Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
630
631 SPAN_ATTACH(Tracer, FileAndOccurrences.first(),
632 static_cast<int64_t>(Ranges.size()));
633 }
634 return AffectedFiles;
635}
636
637// Index-based rename, it renames all occurrences outside of the main file.
638//
639// The cross-file rename is purely based on the index, as we don't want to
640// build all ASTs for affected files, which may cause a performance hit.
641// We choose to trade off some correctness for performance and scalability.
642//
643// Clangd builds a dynamic index for all opened files on top of the static
644// index of the whole codebase. Dynamic index is up-to-date (respects dirty
645// buffers) as long as clangd finishes processing opened files, while static
646// index (background index) is relatively stale. We choose the dirty buffers
647// as the file content we rename on, and fallback to file content on disk if
648// there is no dirty buffer.
649llvm::Expected<FileEdits>
650renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
651 llvm::StringRef NewName, const SymbolIndex &Index,
652 size_t MaxLimitFiles, llvm::vfs::FileSystem &FS) {
653 trace::Span Tracer("RenameOutsideFile");
654 auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
655 Index, MaxLimitFiles);
656 if (!AffectedFiles)
657 return AffectedFiles.takeError();
659 for (auto &FileAndOccurrences : *AffectedFiles) {
660 llvm::StringRef FilePath = FileAndOccurrences.first();
661
662 auto ExpBuffer = FS.getBufferForFile(FilePath);
663 if (!ExpBuffer) {
664 elog("Fail to read file content: Fail to open file {0}: {1}", FilePath,
665 ExpBuffer.getError().message());
666 continue;
667 }
668
669 auto AffectedFileCode = (*ExpBuffer)->getBuffer();
670 auto RenameRanges =
671 adjustRenameRanges(AffectedFileCode, RenameDecl.getNameAsString(),
672 std::move(FileAndOccurrences.second),
673 RenameDecl.getASTContext().getLangOpts());
674 if (!RenameRanges) {
675 // Our heuristics fails to adjust rename ranges to the current state of
676 // the file, it is most likely the index is stale, so we give up the
677 // entire rename.
678 return error("Index results don't match the content of file {0} "
679 "(the index may be stale)",
680 FilePath);
681 }
682 auto RenameEdit =
683 buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewName);
684 if (!RenameEdit)
685 return error("failed to rename in file {0}: {1}", FilePath,
686 RenameEdit.takeError());
687 if (!RenameEdit->Replacements.empty())
688 Results.insert({FilePath, std::move(*RenameEdit)});
689 }
690 return Results;
691}
692
693// A simple edit is either changing line or column, but not both.
694bool impliesSimpleEdit(const Position &LHS, const Position &RHS) {
695 return LHS.line == RHS.line || LHS.character == RHS.character;
696}
697
698// Performs a DFS to enumerate all possible near-miss matches.
699// It finds the locations where the indexed occurrences are now spelled in
700// Lexed occurrences, a near miss is defined as:
701// - a near miss maps all of the **name** occurrences from the index onto a
702// *subset* of lexed occurrences (we allow a single name refers to more
703// than one symbol)
704// - all indexed occurrences must be mapped, and Result must be distinct and
705// preserve order (only support detecting simple edits to ensure a
706// robust mapping)
707// - each indexed -> lexed occurrences mapping correspondence may change the
708// *line* or *column*, but not both (increases chance of a robust mapping)
709void findNearMiss(
710 std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
711 ArrayRef<Range> LexedRest, int LexedIndex, int &Fuel,
712 llvm::function_ref<void(const std::vector<size_t> &)> MatchedCB) {
713 if (--Fuel < 0)
714 return;
715 if (IndexedRest.size() > LexedRest.size())
716 return;
717 if (IndexedRest.empty()) {
718 MatchedCB(PartialMatch);
719 return;
720 }
721 if (impliesSimpleEdit(IndexedRest.front().start, LexedRest.front().start)) {
722 PartialMatch.push_back(LexedIndex);
723 findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
724 LexedIndex + 1, Fuel, MatchedCB);
725 PartialMatch.pop_back();
726 }
727 findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
728 LexedIndex + 1, Fuel, MatchedCB);
729}
730
731} // namespace
732
733llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
734 assert(!RInputs.Index == !RInputs.FS &&
735 "Index and FS must either both be specified or both null.");
736 trace::Span Tracer("Rename flow");
737 const auto &Opts = RInputs.Opts;
738 ParsedAST &AST = RInputs.AST;
739 const SourceManager &SM = AST.getSourceManager();
740 llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
741 // Try to find the tokens adjacent to the cursor position.
742 auto Loc = sourceLocationInMainFile(SM, RInputs.Pos);
743 if (!Loc)
744 return Loc.takeError();
745 const syntax::Token *IdentifierToken =
746 spelledIdentifierTouching(*Loc, AST.getTokens());
747
748 // Renames should only triggered on identifiers.
749 if (!IdentifierToken)
750 return makeError(ReasonToReject::NoSymbolFound);
751 Range CurrentIdentifier = halfOpenToRange(
752 SM, CharSourceRange::getCharRange(IdentifierToken->location(),
753 IdentifierToken->endLocation()));
754 // FIXME: Renaming macros is not supported yet, the macro-handling code should
755 // be moved to rename tooling library.
756 if (locateMacroAt(*IdentifierToken, AST.getPreprocessor()))
757 return makeError(ReasonToReject::UnsupportedSymbol);
758
759 auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location());
760 filterRenameTargets(DeclsUnderCursor);
761 if (DeclsUnderCursor.empty())
762 return makeError(ReasonToReject::NoSymbolFound);
763 if (DeclsUnderCursor.size() > 1)
764 return makeError(ReasonToReject::AmbiguousSymbol);
765 const auto &RenameDecl = **DeclsUnderCursor.begin();
766 const auto *ID = RenameDecl.getIdentifier();
767 if (!ID)
768 return makeError(ReasonToReject::UnsupportedSymbol);
769 if (ID->getName() == RInputs.NewName)
770 return makeError(ReasonToReject::SameName);
771 auto Invalid = checkName(RenameDecl, RInputs.NewName);
772 if (Invalid)
773 return makeError(std::move(*Invalid));
774
775 auto Reject =
776 renameable(RenameDecl, RInputs.MainFilePath, RInputs.Index, Opts);
777 if (Reject)
778 return makeError(*Reject);
779
780 // We have two implementations of the rename:
781 // - AST-based rename: used for renaming local symbols, e.g. variables
782 // defined in a function body;
783 // - index-based rename: used for renaming non-local symbols, and not
784 // feasible for local symbols (as by design our index don't index these
785 // symbols by design;
786 // To make cross-file rename work for local symbol, we use a hybrid solution:
787 // - run AST-based rename on the main file;
788 // - run index-based rename on other affected files;
789 auto MainFileRenameEdit = renameWithinFile(AST, RenameDecl, RInputs.NewName);
790 if (!MainFileRenameEdit)
791 return MainFileRenameEdit.takeError();
792
793 // Check the rename-triggering location is actually being renamed.
794 // This is a robustness check to avoid surprising rename results -- if the
795 // the triggering location is not actually the name of the node we identified
796 // (e.g. for broken code), then rename is likely not what users expect, so we
797 // reject this kind of rename.
798 auto StartOffset = positionToOffset(MainFileCode, CurrentIdentifier.start);
799 auto EndOffset = positionToOffset(MainFileCode, CurrentIdentifier.end);
800 if (!StartOffset)
801 return StartOffset.takeError();
802 if (!EndOffset)
803 return EndOffset.takeError();
804 if (llvm::none_of(
805 *MainFileRenameEdit,
806 [&StartOffset, &EndOffset](const clang::tooling::Replacement &R) {
807 return R.getOffset() == *StartOffset &&
808 R.getLength() == *EndOffset - *StartOffset;
809 })) {
810 return makeError(ReasonToReject::NoSymbolFound);
811 }
812 RenameResult Result;
813 Result.Target = CurrentIdentifier;
814 Edit MainFileEdits = Edit(MainFileCode, std::move(*MainFileRenameEdit));
815 for (const TextEdit &TE : MainFileEdits.asTextEdits())
816 Result.LocalChanges.push_back(TE.range);
817
818 // return the main file edit if this is a within-file rename or the symbol
819 // being renamed is function local.
820 if (RenameDecl.getParentFunctionOrMethod()) {
821 Result.GlobalChanges = FileEdits(
822 {std::make_pair(RInputs.MainFilePath, std::move(MainFileEdits))});
823 return Result;
824 }
825
826 // If the index is nullptr, we don't know the completeness of the result, so
827 // we don't populate the field GlobalChanges.
828 if (!RInputs.Index) {
829 assert(Result.GlobalChanges.empty());
830 return Result;
831 }
832
833 auto OtherFilesEdits = renameOutsideFile(
834 RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index,
835 Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
836 : Opts.LimitFiles,
837 *RInputs.FS);
838 if (!OtherFilesEdits)
839 return OtherFilesEdits.takeError();
840 Result.GlobalChanges = *OtherFilesEdits;
841 // Attach the rename edits for the main file.
842 Result.GlobalChanges.try_emplace(RInputs.MainFilePath,
843 std::move(MainFileEdits));
844 return Result;
845}
846
847llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
848 llvm::StringRef InitialCode,
849 std::vector<Range> Occurrences,
850 llvm::StringRef NewName) {
851 trace::Span Tracer("BuildRenameEdit");
852 SPAN_ATTACH(Tracer, "file_path", AbsFilePath);
853 SPAN_ATTACH(Tracer, "rename_occurrences",
854 static_cast<int64_t>(Occurrences.size()));
855
856 assert(llvm::is_sorted(Occurrences));
857 assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
858 Occurrences.end() &&
859 "Occurrences must be unique");
860
861 // These two always correspond to the same position.
862 Position LastPos{0, 0};
863 size_t LastOffset = 0;
864
865 auto Offset = [&](const Position &P) -> llvm::Expected<size_t> {
866 assert(LastPos <= P && "malformed input");
867 Position Shifted = {
868 P.line - LastPos.line,
869 P.line > LastPos.line ? P.character : P.character - LastPos.character};
870 auto ShiftedOffset =
871 positionToOffset(InitialCode.substr(LastOffset), Shifted);
872 if (!ShiftedOffset)
873 return error("fail to convert the position {0} to offset ({1})", P,
874 ShiftedOffset.takeError());
875 LastPos = P;
876 LastOffset += *ShiftedOffset;
877 return LastOffset;
878 };
879
880 std::vector<std::pair</*start*/ size_t, /*end*/ size_t>> OccurrencesOffsets;
881 for (const auto &R : Occurrences) {
882 auto StartOffset = Offset(R.start);
883 if (!StartOffset)
884 return StartOffset.takeError();
885 auto EndOffset = Offset(R.end);
886 if (!EndOffset)
887 return EndOffset.takeError();
888 OccurrencesOffsets.push_back({*StartOffset, *EndOffset});
889 }
890
891 tooling::Replacements RenameEdit;
892 for (const auto &R : OccurrencesOffsets) {
893 auto ByteLength = R.second - R.first;
894 if (auto Err = RenameEdit.add(
895 tooling::Replacement(AbsFilePath, R.first, ByteLength, NewName)))
896 return std::move(Err);
897 }
898 return Edit(InitialCode, std::move(RenameEdit));
899}
900
901// Details:
902// - lex the draft code to get all rename candidates, this yields a superset
903// of candidates.
904// - apply range patching heuristics to generate "authoritative" occurrences,
905// cases we consider:
906// (a) index returns a subset of candidates, we use the indexed results.
907// - fully equal, we are sure the index is up-to-date
908// - proper subset, index is correct in most cases? there may be false
909// positives (e.g. candidates got appended), but rename is still safe
910// (b) index returns non-candidate results, we attempt to map the indexed
911// ranges onto candidates in a plausible way (e.g. guess that lines
912// were inserted). If such a "near miss" is found, the rename is still
913// possible
914std::optional<std::vector<Range>>
915adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
916 std::vector<Range> Indexed, const LangOptions &LangOpts) {
917 trace::Span Tracer("AdjustRenameRanges");
918 assert(!Indexed.empty());
919 assert(llvm::is_sorted(Indexed));
920 std::vector<Range> Lexed =
921 collectIdentifierRanges(Identifier, DraftCode, LangOpts);
922 llvm::sort(Lexed);
923 return getMappedRanges(Indexed, Lexed);
924}
925
926std::optional<std::vector<Range>> getMappedRanges(ArrayRef<Range> Indexed,
927 ArrayRef<Range> Lexed) {
928 trace::Span Tracer("GetMappedRanges");
929 assert(!Indexed.empty());
930 assert(llvm::is_sorted(Indexed));
931 assert(llvm::is_sorted(Lexed));
932
933 if (Indexed.size() > Lexed.size()) {
934 vlog("The number of lexed occurrences is less than indexed occurrences");
936 Tracer, "error",
937 "The number of lexed occurrences is less than indexed occurrences");
938 return std::nullopt;
939 }
940 // Fast check for the special subset case.
941 if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
942 return Indexed.vec();
943
944 std::vector<size_t> Best;
945 size_t BestCost = std::numeric_limits<size_t>::max();
946 bool HasMultiple = false;
947 std::vector<size_t> ResultStorage;
948 int Fuel = 10000;
949 findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
950 [&](const std::vector<size_t> &Matched) {
951 size_t MCost =
952 renameRangeAdjustmentCost(Indexed, Lexed, Matched);
953 if (MCost < BestCost) {
954 BestCost = MCost;
955 Best = std::move(Matched);
956 HasMultiple = false; // reset
957 return;
958 }
959 if (MCost == BestCost)
960 HasMultiple = true;
961 });
962 if (HasMultiple) {
963 vlog("The best near miss is not unique.");
964 SPAN_ATTACH(Tracer, "error", "The best near miss is not unique");
965 return std::nullopt;
966 }
967 if (Best.empty()) {
968 vlog("Didn't find a near miss.");
969 SPAN_ATTACH(Tracer, "error", "Didn't find a near miss");
970 return std::nullopt;
971 }
972 std::vector<Range> Mapped;
973 for (auto I : Best)
974 Mapped.push_back(Lexed[I]);
975 SPAN_ATTACH(Tracer, "mapped_ranges", static_cast<int64_t>(Mapped.size()));
976 return Mapped;
977}
978
979// The cost is the sum of the implied edit sizes between successive diffs, only
980// simple edits are considered:
981// - insert/remove a line (change line offset)
982// - insert/remove a character on an existing line (change column offset)
983//
984// Example I, total result is 1 + 1 = 2.
985// diff[0]: line + 1 <- insert a line before edit 0.
986// diff[1]: line + 1
987// diff[2]: line + 1
988// diff[3]: line + 2 <- insert a line before edits 2 and 3.
989//
990// Example II, total result is 1 + 1 + 1 = 3.
991// diff[0]: line + 1 <- insert a line before edit 0.
992// diff[1]: column + 1 <- remove a line between edits 0 and 1, and insert a
993// character on edit 1.
994size_t renameRangeAdjustmentCost(ArrayRef<Range> Indexed, ArrayRef<Range> Lexed,
995 ArrayRef<size_t> MappedIndex) {
996 assert(Indexed.size() == MappedIndex.size());
997 assert(llvm::is_sorted(Indexed));
998 assert(llvm::is_sorted(Lexed));
999
1000 int LastLine = -1;
1001 int LastDLine = 0, LastDColumn = 0;
1002 int Cost = 0;
1003 for (size_t I = 0; I < Indexed.size(); ++I) {
1004 int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line;
1005 int DColumn =
1006 Indexed[I].start.character - Lexed[MappedIndex[I]].start.character;
1007 int Line = Indexed[I].start.line;
1008 if (Line != LastLine)
1009 LastDColumn = 0; // column offsets don't carry cross lines.
1010 Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
1011 std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn);
1012 }
1013 return Cost;
1014}
1015
1016} // namespace clangd
1017} // namespace clang
const FunctionDecl * Decl
BindArgumentKind Kind
size_t Offset
std::vector< CodeCompletionResult > Results
const FunctionDecl * EnclosingFunction
const Node * Parent
const Criteria C
SmallVector< Detail, DefaultLimit > Details
ExpectedMatch Candidate
std::string MainFile
CharSourceRange Range
SourceRange for the file name.
SourceLocation Loc
unsigned int LastLine
Token Name
Kind K
Definition: Rename.cpp:454
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Definition: Trace.h:164
Stores and provides access to parsed AST.
Definition: ParsedAST.h:47
static SelectionTree createRight(ASTContext &AST, const syntax::TokenBuffer &Tokens, unsigned Begin, unsigned End)
Definition: Selection.cpp:1060
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
virtual void relations(const RelationsRequest &Req, llvm::function_ref< void(const SymbolID &Subject, const Symbol &Object)> Callback) const =0
Finds all relations (S, P, O) stored in the index such that S is among Req.Subjects and P is Req....
virtual bool refs(const RefsRequest &Req, llvm::function_ref< void(const Ref &)> Callback) const =0
Finds all occurrences (e.g.
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Resolves the absolute path of U.
Definition: URI.cpp:245
Records an event whose duration is the lifetime of the Span object.
Definition: Trace.h:143
Range toRange(llvm::SMRange R, const llvm::SourceMgr &SM)
Definition: ConfigTesting.h:82
llvm::Expected< Edit > buildRenameEdit(llvm::StringRef AbsFilePath, llvm::StringRef InitialCode, std::vector< Range > Occurrences, llvm::StringRef NewName)
Generates rename edits that replaces all given occurrences with the NewName.
Definition: Rename.cpp:847
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Definition: AST.cpp:348
Range halfOpenToRange(const SourceManager &SM, CharSourceRange R)
Definition: SourceCode.cpp:467
std::string Path
A typedef to represent a file path.
Definition: Path.h:26
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
Definition: SourceCode.cpp:418
llvm::StringMap< Edit > FileEdits
A mapping from absolute file path (the one used for accessing the underlying VFS) to edits.
Definition: SourceCode.h:205
void vlog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:72
static const char * toString(OffsetEncoding OE)
Definition: Protocol.cpp:1412
std::optional< std::vector< Range > > adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier, std::vector< Range > Indexed, const LangOptions &LangOpts)
Adjusts indexed occurrences to match the current state of the file.
Definition: Rename.cpp:915
llvm::Expected< RenameResult > rename(const RenameInputs &RInputs)
Renames all occurrences of the symbol.
Definition: Rename.cpp:733
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out, const HeuristicResolver *Resolver)
Recursively traverse S and report all references explicitly written in the code.
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
Definition: Logger.h:79
llvm::SmallVector< const NamedDecl *, 1 > targetDecl(const DynTypedNode &N, DeclRelationSet Mask, const HeuristicResolver *Resolver)
targetDecl() finds the declaration referred to by an AST node.
Definition: FindTarget.cpp:564
std::optional< DefinedMacro > locateMacroAt(const syntax::Token &SpelledTok, Preprocessor &PP)
Gets the macro referenced by SpelledTok.
Definition: SourceCode.cpp:983
std::optional< std::vector< Range > > getMappedRanges(ArrayRef< Range > Indexed, ArrayRef< Range > Lexed)
Calculates the lexed occurrences that the given indexed occurrences map to.
Definition: Rename.cpp:926
size_t renameRangeAdjustmentCost(ArrayRef< Range > Indexed, ArrayRef< Range > Lexed, ArrayRef< size_t > MappedIndex)
Evaluates how good the mapped result is.
Definition: Rename.cpp:994
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:172
std::vector< Range > collectIdentifierRanges(llvm::StringRef Identifier, llvm::StringRef Content, const LangOptions &LangOpts)
Collects all ranges of the given identifier in the source code.
Definition: SourceCode.cpp:630
llvm::Expected< SourceLocation > sourceLocationInMainFile(const SourceManager &SM, Position P)
Return the file location, corresponding to P.
Definition: SourceCode.cpp:457
@ Parameter
An inlay hint that is for a parameter.
bool isKeyword(llvm::StringRef NewName, const LangOptions &LangOpts)
Return true if the TokenName is in the list of reversed keywords of the language.
Definition: SourceCode.cpp:643
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:61
bool isProtoFile(SourceLocation Loc, const SourceManager &SM)
Returns true if the given location is in a generated protobuf file.
@ TemplatePattern
This is the pattern the template specialization was instantiated from.
@ Alias
This declaration is an alias that was referred to.
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
constexpr llvm::StringLiteral Message
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
A set of edits generated for a single file.
Definition: SourceCode.h:185
std::vector< TextEdit > asTextEdits() const
Represents Replacements as TextEdits that are available for use in LSP.
int line
Line position in a document (zero-based).
Definition: Protocol.h:158
Position start
The range's start position.
Definition: Protocol.h:187
Position end
The range's end position.
Definition: Protocol.h:190
llvm::StringRef NewName
Definition: Rename.h:38
const SymbolIndex * Index
Definition: Rename.h:48
llvm::StringRef MainFilePath
Definition: Rename.h:41
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS
Definition: Rename.h:46
RenameOptions Opts
Definition: Rename.h:50
@ Counter
An aggregate number whose rate of change over time is meaningful.
Definition: Trace.h:46