#include "refactor/Rename.h"
#include "AST.h"
#include "FindTarget.h"
#include "ParsedAST.h"
#include "Selection.h"
#include "SourceCode.h"
#include "index/SymbolCollector.h"
#include "support/Logger.h"
#include "support/Trace.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include <algorithm>
#include <optional>
namespace clang {
namespace clangd {
namespace {
std::optional<std::string> filePath(const SymbolLocation &Loc,
llvm::StringRef HintFilePath) {
if (!Loc)
return std::nullopt;
auto Path = URI::resolve(Loc.FileURI, HintFilePath);
if (!Path) {
elog("Could not resolve URI {0}: {1}", Loc.FileURI, Path.takeError());
return std::nullopt;
}
return *Path;
}
bool isInMacroBody(const SourceManager &SM, SourceLocation Loc) {
while (Loc.isMacroID()) {
if (SM.isMacroBodyExpansion(Loc))
return true;
Loc = SM.getImmediateMacroCallerLoc(Loc);
}
return false;
}
const NamedDecl *canonicalRenameDecl(const NamedDecl *D) {
if (const auto *VarTemplate = dyn_cast<VarTemplateSpecializationDecl>(D))
return canonicalRenameDecl(
VarTemplate->getSpecializedTemplate()->getTemplatedDecl());
if (const auto *Template = dyn_cast<TemplateDecl>(D))
if (const NamedDecl *TemplatedDecl = Template->getTemplatedDecl())
return canonicalRenameDecl(TemplatedDecl);
if (const auto *ClassTemplateSpecialization =
dyn_cast<ClassTemplateSpecializationDecl>(D))
return canonicalRenameDecl(
ClassTemplateSpecialization->getSpecializedTemplate()
->getTemplatedDecl());
if (const auto *Method = dyn_cast<CXXMethodDecl>(D)) {
if (Method->getDeclKind() == Decl::Kind::CXXConstructor ||
Method->getDeclKind() == Decl::Kind::CXXDestructor)
return canonicalRenameDecl(Method->getParent());
if (const FunctionDecl *InstantiatedMethod =
Method->getInstantiatedFromMemberFunction())
return canonicalRenameDecl(InstantiatedMethod);
if (Method->isVirtual() && Method->size_overridden_methods())
return canonicalRenameDecl(*Method->overridden_methods().begin());
}
if (const auto *Function = dyn_cast<FunctionDecl>(D))
if (const FunctionTemplateDecl *Template = Function->getPrimaryTemplate())
return canonicalRenameDecl(Template);
if (const auto *Field = dyn_cast<FieldDecl>(D)) {
const auto *FieldParent =
dyn_cast_or_null<CXXRecordDecl>(Field->getParent());
if (!FieldParent)
return Field->getCanonicalDecl();
FieldParent = FieldParent->getTemplateInstantiationPattern();
if (!FieldParent || Field->getParent() == FieldParent)
return Field->getCanonicalDecl();
for (const FieldDecl *Candidate : FieldParent->fields())
if (Field->getDeclName() == Candidate->getDeclName())
return Candidate->getCanonicalDecl();
elog("FieldParent should have field with the same name as Field.");
}
if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (const VarDecl *OriginalVD = VD->getInstantiatedFromStaticDataMember())
return canonicalRenameDecl(OriginalVD);
}
if (const auto *UD = dyn_cast<UsingShadowDecl>(D)) {
if (const auto *TargetDecl = UD->getTargetDecl())
return canonicalRenameDecl(TargetDecl);
}
return dyn_cast<NamedDecl>(D->getCanonicalDecl());
}
const NamedDecl *pickInterestingTarget(const NamedDecl *D) {
if (const auto *CD = dyn_cast<ObjCCategoryDecl>(D))
if (const auto CI = CD->getClassInterface())
return CI;
return D;
}
llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
SourceLocation TokenStartLoc) {
unsigned Offset =
AST.getSourceManager().getDecomposedSpellingLoc(TokenStartLoc).second;
SelectionTree Selection = SelectionTree::createRight(
AST.getASTContext(), AST.getTokens(), Offset, Offset);
const SelectionTree::Node *SelectedNode = Selection.commonAncestor();
if (!SelectedNode)
return {};
llvm::DenseSet<const NamedDecl *> Result;
for (const NamedDecl *D :
targetDecl(SelectedNode->ASTNode,
DeclRelation::Alias | DeclRelation::TemplatePattern,
AST.getHeuristicResolver())) {
D = pickInterestingTarget(D);
Result.insert(canonicalRenameDecl(D));
}
return Result;
}
void filterRenameTargets(llvm::DenseSet<const NamedDecl *> &Decls) {
auto UD = std::find_if(Decls.begin(), Decls.end(), [](const NamedDecl *D) {
return llvm::isa<UsingDecl>(D);
});
if (UD != Decls.end()) {
Decls.erase(UD);
}
}
bool isExcluded(const NamedDecl &RenameDecl) {
const auto &SM = RenameDecl.getASTContext().getSourceManager();
return SM.isInSystemHeader(RenameDecl.getLocation()) ||
isProtoFile(RenameDecl.getLocation(), SM);
}
enum class ReasonToReject {
NoSymbolFound,
NoIndexProvided,
NonIndexable,
UnsupportedSymbol,
AmbiguousSymbol,
SameName,
};
std::optional<ReasonToReject> renameable(const NamedDecl &RenameDecl,
StringRef MainFilePath,
const SymbolIndex *Index,
const RenameOptions &Opts) {
trace::Span Tracer("Renameable");
if (!Opts.RenameVirtual) {
if (const auto *S = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl)) {
if (S->isVirtual())
return ReasonToReject::UnsupportedSymbol;
}
}
const auto *ID = RenameDecl.getIdentifier();
if (!ID && !isa<ObjCMethodDecl>(&RenameDecl))
return ReasonToReject::UnsupportedSymbol;
if (llvm::isa<NamespaceDecl>(&RenameDecl))
return ReasonToReject::UnsupportedSymbol;
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(&RenameDecl)) {
if (FD->isOverloadedOperator())
return ReasonToReject::UnsupportedSymbol;
}
if (RenameDecl.getParentFunctionOrMethod())
return std::nullopt;
if (isExcluded(RenameDecl))
return ReasonToReject::UnsupportedSymbol;
auto &ASTCtx = RenameDecl.getASTContext();
bool MainFileIsHeader = isHeaderFile(MainFilePath, ASTCtx.getLangOpts());
bool DeclaredInMainFile =
isInsideMainFile(RenameDecl.getBeginLoc(), ASTCtx.getSourceManager());
bool IsMainFileOnly = true;
if (MainFileIsHeader)
IsMainFileOnly = false;
else if (!DeclaredInMainFile)
IsMainFileOnly = false;
if (!SymbolCollector::shouldCollectSymbol(
RenameDecl, RenameDecl.getASTContext(), SymbolCollector::Options(),
IsMainFileOnly))
return ReasonToReject::NonIndexable;
return std::nullopt;
}
llvm::Error makeError(ReasonToReject Reason) {
auto Message = [](ReasonToReject Reason) {
switch (Reason) {
case ReasonToReject::NoSymbolFound:
return "there is no symbol at the given location";
case ReasonToReject::NoIndexProvided:
return "no index provided";
case ReasonToReject::NonIndexable:
return "symbol may be used in other files (not eligible for indexing)";
case ReasonToReject::UnsupportedSymbol:
return "symbol is not a supported kind (e.g. namespace, macro)";
case ReasonToReject::AmbiguousSymbol:
return "there are multiple symbols at the given location";
case ReasonToReject::SameName:
return "new name is the same as the old name";
}
llvm_unreachable("unhandled reason kind");
};
return error("Cannot rename symbol: {0}", Message(Reason));
}
std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
const NamedDecl &ND) {
trace::Span Tracer("FindOccurrencesWithinFile");
assert(canonicalRenameDecl(&ND) == &ND &&
"ND should be already canonicalized.");
std::vector<SourceLocation> Results;
for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
findExplicitReferences(
TopLevelDecl,
[&](ReferenceLoc Ref) {
if (Ref.Targets.empty())
return;
for (const auto *Target : Ref.Targets) {
if (canonicalRenameDecl(Target) == &ND) {
Results.push_back(Ref.NameLoc);
return;
}
}
},
AST.getHeuristicResolver());
}
return Results;
}
const NamedDecl *lookupSiblingWithinEnclosingScope(ASTContext &Ctx,
const NamedDecl &RenamedDecl,
StringRef NewName) {
DynTypedNodeList Storage(DynTypedNode::create(RenamedDecl));
auto GetSingleParent = [&](const DynTypedNode &Node) -> const DynTypedNode * {
Storage = Ctx.getParents(Node);
return (Storage.size() == 1) ? Storage.begin() : nullptr;
};
const auto *Parent = GetSingleParent(DynTypedNode::create(RenamedDecl));
if (!Parent || !(Parent->get<DeclStmt>() || Parent->get<TypeLoc>()))
return nullptr;
Parent = GetSingleParent(*Parent);
auto CheckDeclStmt = [&](const DeclStmt *DS,
StringRef Name) -> const NamedDecl * {
if (!DS)
return nullptr;
for (const auto &Child : DS->getDeclGroup())
if (const auto *ND = dyn_cast<NamedDecl>(Child))
if (ND != &RenamedDecl && ND->getDeclName().isIdentifier() &&
ND->getName() == Name)
return ND;
return nullptr;
};
auto CheckCompoundStmt = [&](const Stmt *S,
StringRef Name) -> const NamedDecl * {
if (const auto *CS = dyn_cast_or_null<CompoundStmt>(S))
for (const auto *Node : CS->children())
if (const auto *Result = CheckDeclStmt(dyn_cast<DeclStmt>(Node), Name))
return Result;
return nullptr;
};
auto CheckConditionVariable = [&](const auto *Scope,
StringRef Name) -> const NamedDecl * {
if (!Scope)
return nullptr;
return CheckDeclStmt(Scope->getConditionVariableDeclStmt(), Name);
};
if (const auto *EnclosingCS = Parent->get<CompoundStmt>()) {
if (const auto *Result = CheckCompoundStmt(EnclosingCS, NewName))
return Result;
const auto *ScopeParent = GetSingleParent(*Parent);
if (const auto *Result =
CheckConditionVariable(ScopeParent->get<IfStmt>(), NewName))
return Result;
if (const auto *Result =
CheckConditionVariable(ScopeParent->get<WhileStmt>(), NewName))
return Result;
if (const auto *For = ScopeParent->get<ForStmt>())
if (const auto *Result = CheckDeclStmt(
dyn_cast_or_null<DeclStmt>(For->getInit()), NewName))
return Result;
if (const auto *Function = ScopeParent->get<FunctionDecl>())
for (const auto *Parameter : Function->parameters())
if (Parameter->getName() == NewName)
return Parameter;
return nullptr;
}
if (const auto *EnclosingIf = Parent->get<IfStmt>()) {
if (const auto *Result = CheckCompoundStmt(EnclosingIf->getElse(), NewName))
return Result;
return CheckCompoundStmt(EnclosingIf->getThen(), NewName);
}
if (const auto *EnclosingWhile = Parent->get<WhileStmt>())
return CheckCompoundStmt(EnclosingWhile->getBody(), NewName);
if (const auto *EnclosingFor = Parent->get<ForStmt>()) {
if (const auto *Result = CheckDeclStmt(
dyn_cast_or_null<DeclStmt>(EnclosingFor->getInit()), NewName))
return Result;
return CheckCompoundStmt(EnclosingFor->getBody(), NewName);
}
if (const auto *EnclosingFunction = Parent->get<FunctionDecl>()) {
for (const auto *Parameter : EnclosingFunction->parameters())
if (Parameter != &RenamedDecl && Parameter->getName() == NewName)
return Parameter;
if (!EnclosingFunction->doesThisDeclarationHaveABody())
return nullptr;
return CheckCompoundStmt(EnclosingFunction->getBody(), NewName);
}
return nullptr;
}
const NamedDecl *lookupSiblingsWithinContext(ASTContext &Ctx,
const NamedDecl &RenamedDecl,
llvm::StringRef NewName) {
const auto &II = Ctx.Idents.get(NewName);
DeclarationName LookupName(&II);
DeclContextLookupResult LookupResult;
const auto *DC = RenamedDecl.getDeclContext();
while (DC->isTransparentContext())
DC = DC->getParent();
switch (DC->getDeclKind()) {
case Decl::TranslationUnit:
case Decl::Namespace:
case Decl::Record:
case Decl::Enum:
case Decl::CXXRecord:
LookupResult = DC->lookup(LookupName);
break;
default:
break;
}
for (const auto *D : LookupResult)
if (D->getCanonicalDecl() != RenamedDecl.getCanonicalDecl())
return D;
return nullptr;
}
const NamedDecl *lookupSiblingWithName(ASTContext &Ctx,
const NamedDecl &RenamedDecl,
llvm::StringRef NewName) {
trace::Span Tracer("LookupSiblingWithName");
if (const auto *Result =
lookupSiblingsWithinContext(Ctx, RenamedDecl, NewName))
return Result;
return lookupSiblingWithinEnclosingScope(Ctx, RenamedDecl, NewName);
}
struct InvalidName {
enum Kind {
Keywords,
Conflict,
BadIdentifier,
};
Kind K;
std::string Details;
};
std::string toString(InvalidName::Kind K) {
switch (K) {
case InvalidName::Keywords:
return "Keywords";
case InvalidName::Conflict:
return "Conflict";
case InvalidName::BadIdentifier:
return "BadIdentifier";
}
llvm_unreachable("unhandled InvalidName kind");
}
llvm::Error makeError(InvalidName Reason) {
auto Message = [](const InvalidName &Reason) {
switch (Reason.K) {
case InvalidName::Keywords:
return llvm::formatv("the chosen name \"{0}\" is a keyword",
Reason.Details);
case InvalidName::Conflict:
return llvm::formatv("conflict with the symbol in {0}", Reason.Details);
case InvalidName::BadIdentifier:
return llvm::formatv("the chosen name \"{0}\" is not a valid identifier",
Reason.Details);
}
llvm_unreachable("unhandled InvalidName kind");
};
return error("invalid name: {0}", Message(Reason));
}
static bool mayBeValidIdentifier(llvm::StringRef Ident, bool AllowColon) {
assert(llvm::json::isUTF8(Ident));
if (Ident.empty())
return false;
bool AllowDollar = true;
if (llvm::isASCII(Ident.front()) &&
!isAsciiIdentifierStart(Ident.front(), AllowDollar))
return false;
for (char C : Ident) {
if (AllowColon && C == ':')
continue;
if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar))
return false;
}
return true;
}
std::string getName(const NamedDecl &RenameDecl) {
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl))
return MD->getSelector().getAsString();
if (const auto *ID = RenameDecl.getIdentifier())
return ID->getName().str();
return "";
}
llvm::Error checkName(const NamedDecl &RenameDecl, llvm::StringRef NewName,
llvm::StringRef OldName) {
trace::Span Tracer("CheckName");
static constexpr trace::Metric InvalidNameMetric(
"rename_name_invalid", trace::Metric::Counter, "invalid_kind");
if (OldName == NewName)
return makeError(ReasonToReject::SameName);
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
const auto Sel = MD->getSelector();
if (Sel.getNumArgs() != NewName.count(':') &&
NewName != "__clangd_rename_placeholder")
return makeError(InvalidName{InvalidName::BadIdentifier, NewName.str()});
}
auto &ASTCtx = RenameDecl.getASTContext();
std::optional<InvalidName> Result;
if (isKeyword(NewName, ASTCtx.getLangOpts()))
Result = InvalidName{InvalidName::Keywords, NewName.str()};
else if (!mayBeValidIdentifier(NewName, isa<ObjCMethodDecl>(&RenameDecl)))
Result = InvalidName{InvalidName::BadIdentifier, NewName.str()};
else {
if (RenameDecl.getKind() != Decl::Function &&
RenameDecl.getKind() != Decl::CXXMethod) {
if (auto *Conflict = lookupSiblingWithName(ASTCtx, RenameDecl, NewName))
Result = InvalidName{
InvalidName::Conflict,
Conflict->getLocation().printToString(ASTCtx.getSourceManager())};
}
}
if (Result) {
InvalidNameMetric.record(1, toString(Result->K));
return makeError(*Result);
}
return llvm::Error::success();
}
bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) {
return Cur.kind() == tok::identifier && Next.kind() == tok::colon &&
Cur.endLocation() == Next.location();
}
bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token &Next,
const SourceManager &SM,
llvm::StringRef SelectorName) {
if (SelectorName.empty())
return Cur.kind() == tok::colon;
return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName;
}
std::optional<SymbolRange>
findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
const SourceManager &SM, Selector Sel,
tok::TokenKind Terminator) {
assert(!Tokens.empty());
unsigned NumArgs = Sel.getNumArgs();
llvm::SmallVector<tok::TokenKind, 8> Closes;
std::vector<Range> SelectorPieces;
for (unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
const auto &Tok = Tokens[Index];
if (Closes.empty()) {
auto PieceCount = SelectorPieces.size();
if (PieceCount < NumArgs &&
isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
Sel.getNameForSlot(PieceCount))) {
if (!Sel.getNameForSlot(PieceCount).empty())
++Index;
SelectorPieces.push_back(
halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
continue;
}
if (SelectorPieces.size() >= NumArgs &&
isSelectorLike(Tok, Tokens[Index + 1]))
return std::nullopt;
}
if (Closes.empty() && Tok.kind() == Terminator)
return SelectorPieces.size() == NumArgs
? std::optional(SymbolRange(SelectorPieces))
: std::nullopt;
switch (Tok.kind()) {
case tok::l_square:
Closes.push_back(tok::r_square);
break;
case tok::l_paren:
Closes.push_back(tok::r_paren);
break;
case tok::l_brace:
Closes.push_back(tok::r_brace);
break;
case tok::r_square:
case tok::r_paren:
case tok::r_brace:
if (Closes.empty() || Closes.back() != Tok.kind())
return std::nullopt;
Closes.pop_back();
break;
case tok::semi:
if (Closes.empty())
return SelectorPieces.size() == NumArgs
? std::optional(SymbolRange(SelectorPieces))
: std::nullopt;
break;
default:
break;
}
}
return std::nullopt;
}
std::vector<SymbolRange> collectRenameIdentifierRanges(
llvm::StringRef Identifier, llvm::StringRef Content,
const LangOptions &LangOpts, std::optional<Selector> Selector) {
std::vector<SymbolRange> Ranges;
if (!Selector) {
auto IdentifierRanges =
collectIdentifierRanges(Identifier, Content, LangOpts);
for (const auto &R : IdentifierRanges)
Ranges.emplace_back(R);
return Ranges;
}
std::string NullTerminatedCode = Content.str();
SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode);
auto &SM = FileSM.get();
llvm::SmallVector<tok::TokenKind, 8> Closes;
llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
unsigned Last = Tokens.size() - 1;
for (unsigned Index = 0; Index < Last; ++Index) {
const auto &Tok = Tokens[Index];
if ((Closes.empty() || Closes.back() == tok::r_square) &&
isMatchingSelectorName(Tok, Tokens[Index + 1], SM, FirstSelPiece)) {
auto SelectorRanges =
findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, *Selector,
Closes.empty() ? tok::l_brace : Closes.back());
if (SelectorRanges)
Ranges.emplace_back(std::move(*SelectorRanges));
}
switch (Tok.kind()) {
case tok::l_square:
Closes.push_back(tok::r_square);
break;
case tok::l_paren:
Closes.push_back(tok::r_paren);
break;
case tok::r_square:
case tok::r_paren:
if (Closes.empty())
return std::vector<SymbolRange>();
if (Closes.back() == Tok.kind())
Closes.pop_back();
break;
default:
break;
}
}
return Ranges;
}
clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc,
const SourceManager &SM,
const LangOptions &LangOpts) {
const auto *Token = AST.getTokens().spelledTokenContaining(TokLoc);
assert(Token && "rename expects spelled tokens");
clangd::Range Result;
Result.start = sourceLocToPosition(SM, Token->location());
Result.end = sourceLocToPosition(SM, Token->endLocation());
return Result;
}
llvm::Expected<tooling::Replacements>
renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
llvm::StringRef NewName,
std::vector<SourceLocation> SelectorOccurences) {
const SourceManager &SM = AST.getSourceManager();
auto Code = SM.getBufferData(SM.getMainFileID());
auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
llvm::SmallVector<llvm::StringRef, 8> NewNames;
NewName.split(NewNames, ":");
std::vector<Range> Ranges;
const auto &LangOpts = MD->getASTContext().getLangOpts();
for (const auto &Loc : SelectorOccurences)
Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
auto FilePath = AST.tuPath();
auto RenameRanges = collectRenameIdentifierRanges(
RenameIdentifier, Code, LangOpts, MD->getSelector());
auto RenameEdit = buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
if (!RenameEdit)
return error("failed to rename in file {0}: {1}", FilePath,
RenameEdit.takeError());
return RenameEdit->Replacements;
}
llvm::Expected<tooling::Replacements>
renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
llvm::StringRef NewName) {
trace::Span Tracer("RenameWithinFile");
const SourceManager &SM = AST.getSourceManager();
tooling::Replacements FilteredChanges;
std::vector<SourceLocation> Locs;
for (SourceLocation Loc : findOccurrencesWithinFile(AST, RenameDecl)) {
SourceLocation RenameLoc = Loc;
if (RenameLoc.isMacroID()) {
if (isInMacroBody(SM, RenameLoc))
continue;
RenameLoc = SM.getSpellingLoc(Loc);
}
if (!isInsideMainFile(RenameLoc, SM))
continue;
Locs.push_back(RenameLoc);
}
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
if (MD->getSelector().getNumArgs() > 1)
return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));
NewName.consume_back(":");
}
for (const auto &Loc : Locs) {
if (auto Err = FilteredChanges.add(tooling::Replacement(
SM, CharSourceRange::getTokenRange(Loc), NewName)))
return std::move(Err);
}
return FilteredChanges;
}
Range toRange(const SymbolLocation &L) {
Range R;
R.start.line = L.Start.line();
R.start.character = L.Start.column();
R.end.line = L.End.line();
R.end.character = L.End.column();
return R;
}
void insertTransitiveOverrides(SymbolID Base, llvm::DenseSet<SymbolID> &IDs,
const SymbolIndex &Index) {
RelationsRequest Req;
Req.Predicate = RelationKind::OverriddenBy;
llvm::DenseSet<SymbolID> Pending = {Base};
while (!Pending.empty()) {
Req.Subjects = std::move(Pending);
Pending.clear();
Index.relations(Req, [&](const SymbolID &, const Symbol &Override) {
if (IDs.insert(Override.ID).second)
Pending.insert(Override.ID);
});
}
}
llvm::Expected<llvm::StringMap<std::vector<Range>>>
findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
llvm::StringRef MainFile, const SymbolIndex &Index,
size_t MaxLimitFiles) {
trace::Span Tracer("FindOccurrencesOutsideFile");
RefsRequest RQuest;
RQuest.IDs.insert(getSymbolID(&RenameDecl));
if (const auto *MethodDecl = llvm::dyn_cast<CXXMethodDecl>(&RenameDecl))
if (MethodDecl->isVirtual())
insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index);
llvm::StringMap<std::vector<Range>> AffectedFiles;
bool HasMore = Index.refs(RQuest, [&](const Ref &R) {
if (AffectedFiles.size() >= MaxLimitFiles)
return;
if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
return;
if (auto RefFilePath = filePath(R.Location, MainFile)) {
if (!pathEqual(*RefFilePath, MainFile))
AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
}
});
if (AffectedFiles.size() >= MaxLimitFiles)
return error("The number of affected files exceeds the max limit {0}",
MaxLimitFiles);
if (HasMore)
return error("The symbol {0} has too many occurrences",
RenameDecl.getQualifiedNameAsString());
for (auto &FileAndOccurrences : AffectedFiles) {
auto &Ranges = FileAndOccurrences.getValue();
llvm::sort(Ranges);
Ranges.erase(std::unique(Ranges.begin(), Ranges.end()), Ranges.end());
SPAN_ATTACH(Tracer, FileAndOccurrences.first(),
static_cast<int64_t>(Ranges.size()));
}
return AffectedFiles;
}
llvm::Expected<FileEdits>
renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
llvm::StringRef NewName, const SymbolIndex &Index,
size_t MaxLimitFiles, llvm::vfs::FileSystem &FS) {
trace::Span Tracer("RenameOutsideFile");
auto AffectedFiles = findOccurrencesOutsideFile(RenameDecl, MainFilePath,
Index, MaxLimitFiles);
if (!AffectedFiles)
return AffectedFiles.takeError();
FileEdits Results;
for (auto &FileAndOccurrences : *AffectedFiles) {
llvm::StringRef FilePath = FileAndOccurrences.first();
auto ExpBuffer = FS.getBufferForFile(FilePath);
if (!ExpBuffer) {
elog("Fail to read file content: Fail to open file {0}: {1}", FilePath,
ExpBuffer.getError().message());
continue;
}
std::string RenameIdentifier = RenameDecl.getNameAsString();
std::optional<Selector> Selector = std::nullopt;
llvm::SmallVector<llvm::StringRef, 8> NewNames;
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
if (MD->getSelector().getNumArgs() > 1)
Selector = MD->getSelector();
}
NewName.split(NewNames, ":");
auto AffectedFileCode = (*ExpBuffer)->getBuffer();
auto RenameRanges =
adjustRenameRanges(AffectedFileCode, RenameIdentifier,
std::move(FileAndOccurrences.second),
RenameDecl.getASTContext().getLangOpts(), Selector);
if (!RenameRanges) {
return error("Index results don't match the content of file {0} "
"(the index may be stale)",
FilePath);
}
auto RenameEdit =
buildRenameEdit(FilePath, AffectedFileCode, *RenameRanges, NewNames);
if (!RenameEdit)
return error("failed to rename in file {0}: {1}", FilePath,
RenameEdit.takeError());
if (!RenameEdit->Replacements.empty())
Results.insert({FilePath, std::move(*RenameEdit)});
}
return Results;
}
bool impliesSimpleEdit(const Position &LHS, const Position &RHS) {
return LHS.line == RHS.line || LHS.character == RHS.character;
}
void findNearMiss(
std::vector<size_t> &PartialMatch, ArrayRef<Range> IndexedRest,
ArrayRef<SymbolRange> LexedRest, int LexedIndex, int &Fuel,
llvm::function_ref<void(const std::vector<size_t> &)> MatchedCB) {
if (--Fuel < 0)
return;
if (IndexedRest.size() > LexedRest.size())
return;
if (IndexedRest.empty()) {
MatchedCB(PartialMatch);
return;
}
if (impliesSimpleEdit(IndexedRest.front().start,
LexedRest.front().range().start)) {
PartialMatch.push_back(LexedIndex);
findNearMiss(PartialMatch, IndexedRest.drop_front(), LexedRest.drop_front(),
LexedIndex + 1, Fuel, MatchedCB);
PartialMatch.pop_back();
}
findNearMiss(PartialMatch, IndexedRest, LexedRest.drop_front(),
LexedIndex + 1, Fuel, MatchedCB);
}
}
SymbolRange::SymbolRange(Range R) : Ranges({R}) {}
SymbolRange::SymbolRange(std::vector<Range> Ranges)
: Ranges(std::move(Ranges)) {}
Range SymbolRange::range() const { return Ranges.front(); }
bool operator==(const SymbolRange &LHS, const SymbolRange &RHS) {
return LHS.Ranges == RHS.Ranges;
}
bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS) {
return !(LHS == RHS);
}
bool operator<(const SymbolRange &LHS, const SymbolRange &RHS) {
return LHS.range() < RHS.range();
}
llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
assert(!RInputs.Index == !RInputs.FS &&
"Index and FS must either both be specified or both null.");
trace::Span Tracer("Rename flow");
const auto &Opts = RInputs.Opts;
ParsedAST &AST = RInputs.AST;
const SourceManager &SM = AST.getSourceManager();
llvm::StringRef MainFileCode = SM.getBufferData(SM.getMainFileID());
auto Loc = sourceLocationInMainFile(SM, RInputs.Pos);
if (!Loc)
return Loc.takeError();
const syntax::Token *IdentifierToken =
spelledIdentifierTouching(*Loc, AST.getTokens());
if (!IdentifierToken)
return makeError(ReasonToReject::NoSymbolFound);
Range CurrentIdentifier = halfOpenToRange(
SM, CharSourceRange::getCharRange(IdentifierToken->location(),
IdentifierToken->endLocation()));
if (locateMacroAt(*IdentifierToken, AST.getPreprocessor()))
return makeError(ReasonToReject::UnsupportedSymbol);
auto DeclsUnderCursor = locateDeclAt(AST, IdentifierToken->location());
filterRenameTargets(DeclsUnderCursor);
if (DeclsUnderCursor.empty())
return makeError(ReasonToReject::NoSymbolFound);
if (DeclsUnderCursor.size() > 1)
return makeError(ReasonToReject::AmbiguousSymbol);
const auto &RenameDecl = **DeclsUnderCursor.begin();
static constexpr trace::Metric RenameTriggerCounter(
"rename_trigger_count", trace::Metric::Counter, "decl_kind");
RenameTriggerCounter.record(1, RenameDecl.getDeclKindName());
std::string Placeholder = getName(RenameDecl);
auto Invalid = checkName(RenameDecl, RInputs.NewName, Placeholder);
if (Invalid)
return std::move(Invalid);
auto Reject =
renameable(RenameDecl, RInputs.MainFilePath, RInputs.Index, Opts);
if (Reject)
return makeError(*Reject);
auto MainFileRenameEdit = renameWithinFile(AST, RenameDecl, RInputs.NewName);
if (!MainFileRenameEdit)
return MainFileRenameEdit.takeError();
llvm::DenseSet<Range> RenamedRanges;
if (!isa<ObjCMethodDecl>(RenameDecl)) {
RenamedRanges.insert(CurrentIdentifier);
}
for (const auto &Range : RenamedRanges) {
auto StartOffset = positionToOffset(MainFileCode, Range.start);
auto EndOffset = positionToOffset(MainFileCode, Range.end);
if (!StartOffset)
return StartOffset.takeError();
if (!EndOffset)
return EndOffset.takeError();
if (llvm::none_of(
*MainFileRenameEdit,
[&StartOffset, &EndOffset](const clang::tooling::Replacement &R) {
return R.getOffset() == *StartOffset &&
R.getLength() == *EndOffset - *StartOffset;
})) {
return makeError(ReasonToReject::NoSymbolFound);
}
}
RenameResult Result;
Result.Target = CurrentIdentifier;
Result.Placeholder = Placeholder;
Edit MainFileEdits = Edit(MainFileCode, std::move(*MainFileRenameEdit));
for (const TextEdit &TE : MainFileEdits.asTextEdits())
Result.LocalChanges.push_back(TE.range);
if (RenameDecl.getParentFunctionOrMethod()) {
Result.GlobalChanges = FileEdits(
{std::make_pair(RInputs.MainFilePath, std::move(MainFileEdits))});
return Result;
}
if (!RInputs.Index) {
assert(Result.GlobalChanges.empty());
return Result;
}
auto OtherFilesEdits = renameOutsideFile(
RenameDecl, RInputs.MainFilePath, RInputs.NewName, *RInputs.Index,
Opts.LimitFiles == 0 ? std::numeric_limits<size_t>::max()
: Opts.LimitFiles,
*RInputs.FS);
if (!OtherFilesEdits)
return OtherFilesEdits.takeError();
Result.GlobalChanges = *OtherFilesEdits;
Result.GlobalChanges.try_emplace(RInputs.MainFilePath,
std::move(MainFileEdits));
return Result;
}
llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
llvm::StringRef InitialCode,
std::vector<SymbolRange> Occurrences,
llvm::ArrayRef<llvm::StringRef> NewNames) {
trace::Span Tracer("BuildRenameEdit");
SPAN_ATTACH(Tracer, "file_path", AbsFilePath);
SPAN_ATTACH(Tracer, "rename_occurrences",
static_cast<int64_t>(Occurrences.size()));
assert(llvm::is_sorted(Occurrences));
assert(std::unique(Occurrences.begin(), Occurrences.end()) ==
Occurrences.end() &&
"Occurrences must be unique");
Position LastPos{0, 0};
size_t LastOffset = 0;
auto Offset = [&](const Position &P) -> llvm::Expected<size_t> {
assert(LastPos <= P && "malformed input");
Position Shifted = {
P.line - LastPos.line,
P.line > LastPos.line ? P.character : P.character - LastPos.character};
auto ShiftedOffset =
positionToOffset(InitialCode.substr(LastOffset), Shifted);
if (!ShiftedOffset)
return error("fail to convert the position {0} to offset ({1})", P,
ShiftedOffset.takeError());
LastPos = P;
LastOffset += *ShiftedOffset;
return LastOffset;
};
struct OccurrenceOffset {
size_t Start;
size_t End;
llvm::StringRef NewName;
OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName)
: Start(Start), End(End), NewName(NewName) {}
};
std::vector<OccurrenceOffset> OccurrencesOffsets;
for (const auto &SR : Occurrences) {
for (auto [Range, NewName] : llvm::zip(SR.Ranges, NewNames)) {
auto StartOffset = Offset(Range.start);
if (!StartOffset)
return StartOffset.takeError();
auto EndOffset = Offset(Range.end);
if (!EndOffset)
return EndOffset.takeError();
auto CurName =
InitialCode.substr(*StartOffset, *EndOffset - *StartOffset);
if (CurName == NewName)
continue;
OccurrencesOffsets.emplace_back(*StartOffset, *EndOffset, NewName);
}
}
tooling::Replacements RenameEdit;
for (const auto &R : OccurrencesOffsets) {
auto ByteLength = R.End - R.Start;
if (auto Err = RenameEdit.add(
tooling::Replacement(AbsFilePath, R.Start, ByteLength, R.NewName)))
return std::move(Err);
}
return Edit(InitialCode, std::move(RenameEdit));
}
std::optional<std::vector<SymbolRange>>
adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
std::vector<Range> Indexed, const LangOptions &LangOpts,
std::optional<Selector> Selector) {
trace::Span Tracer("AdjustRenameRanges");
assert(!Indexed.empty());
assert(llvm::is_sorted(Indexed));
std::vector<SymbolRange> Lexed =
collectRenameIdentifierRanges(Identifier, DraftCode, LangOpts, Selector);
llvm::sort(Lexed);
return getMappedRanges(Indexed, Lexed);
}
std::optional<std::vector<SymbolRange>>
getMappedRanges(ArrayRef<Range> Indexed, ArrayRef<SymbolRange> Lexed) {
trace::Span Tracer("GetMappedRanges");
assert(!Indexed.empty());
assert(llvm::is_sorted(Indexed));
assert(llvm::is_sorted(Lexed));
if (Indexed.size() > Lexed.size()) {
vlog("The number of lexed occurrences is less than indexed occurrences");
SPAN_ATTACH(
Tracer, "error",
"The number of lexed occurrences is less than indexed occurrences");
return std::nullopt;
}
if (std::includes(Indexed.begin(), Indexed.end(), Lexed.begin(), Lexed.end()))
return Lexed.vec();
std::vector<size_t> Best;
size_t BestCost = std::numeric_limits<size_t>::max();
bool HasMultiple = false;
std::vector<size_t> ResultStorage;
int Fuel = 10000;
findNearMiss(ResultStorage, Indexed, Lexed, 0, Fuel,
[&](const std::vector<size_t> &Matched) {
size_t MCost =
renameRangeAdjustmentCost(Indexed, Lexed, Matched);
if (MCost < BestCost) {
BestCost = MCost;
Best = std::move(Matched);
HasMultiple = false;
return;
}
if (MCost == BestCost)
HasMultiple = true;
});
if (HasMultiple) {
vlog("The best near miss is not unique.");
SPAN_ATTACH(Tracer, "error", "The best near miss is not unique");
return std::nullopt;
}
if (Best.empty()) {
vlog("Didn't find a near miss.");
SPAN_ATTACH(Tracer, "error", "Didn't find a near miss");
return std::nullopt;
}
std::vector<SymbolRange> Mapped;
for (auto I : Best)
Mapped.push_back(Lexed[I]);
SPAN_ATTACH(Tracer, "mapped_ranges", static_cast<int64_t>(Mapped.size()));
return Mapped;
}
size_t renameRangeAdjustmentCost(ArrayRef<Range> Indexed,
ArrayRef<SymbolRange> Lexed,
ArrayRef<size_t> MappedIndex) {
assert(Indexed.size() == MappedIndex.size());
assert(llvm::is_sorted(Indexed));
assert(llvm::is_sorted(Lexed));
int LastLine = -1;
int LastDLine = 0, LastDColumn = 0;
int Cost = 0;
for (size_t I = 0; I < Indexed.size(); ++I) {
int DLine =
Indexed[I].start.line - Lexed[MappedIndex[I]].range().start.line;
int DColumn = Indexed[I].start.character -
Lexed[MappedIndex[I]].range().start.character;
int Line = Indexed[I].start.line;
if (Line != LastLine)
LastDColumn = 0;
Cost += abs(DLine - LastDLine) + abs(DColumn - LastDColumn);
std::tie(LastLine, LastDLine, LastDColumn) = std::tie(Line, DLine, DColumn);
}
return Cost;
}
}
}