forked from OSchip/llvm-project
[clangd] Implement Decl canonicalization rules for rename
This patch introduces new canonicalization rules which are used for AST-based rename in Clangd. By comparing two canonical declarations of inspected nodes, Clangd determines whether both of them belong to the same entity user would like to rename. Such functionality is relatively concise compared to the Clang-Rename API that is used right now. It also helps to overcome the limitations that Clang-Rename originally had and helps to eliminate several classes of bugs. Clangd AST-based rename currently relies on Clang-Rename which has design limitations and also lacks some features. This patch breaks this dependency and significantly reduces the amount of code to maintain (Clang-Rename is ~2000 LOC, this patch is just <30 LOC of replacement code). We eliminate technical debt by simultaneously * Maintaining feature parity and ensuring no regressions * Opening a straightforward path to improving existing rename bugs * Making it possible to add more capabilities to rename feature which would not be possible with Clang-Rename Reviewed By: hokein Differential Revision: https://reviews.llvm.org/D71880
This commit is contained in:
parent
01c4418544
commit
cf39bdb490
|
@ -18,7 +18,6 @@
|
|||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/DeclTemplate.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h"
|
||||
#include "clang/Tooling/Syntax/Tokens.h"
|
||||
#include "llvm/ADT/None.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
@ -76,6 +75,58 @@ llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
|
|||
return OtherFile;
|
||||
}
|
||||
|
||||
// Canonical declarations help simplify the process of renaming. Examples:
|
||||
// - Template's canonical decl is the templated declaration (i.e.
|
||||
// ClassTemplateDecl is canonicalized to its child CXXRecordDecl,
|
||||
// FunctionTemplateDecl - to child FunctionDecl)
|
||||
// - Given a constructor/destructor, canonical declaration is the parent
|
||||
// CXXRecordDecl because we want to rename both type name and its ctor/dtor.
|
||||
// - All specializations are canonicalized to the primary template. For example:
|
||||
//
|
||||
// template <typename T, int U>
|
||||
// bool Foo = true; (1)
|
||||
//
|
||||
// template <typename T>
|
||||
// bool Foo<T, 0> = true; (2)
|
||||
//
|
||||
// template <>
|
||||
// bool Foo<int, 0> = true; (3)
|
||||
//
|
||||
// Here, both partial (2) and full (3) specializations are canonicalized to (1)
|
||||
// which ensures all three of them are renamed.
|
||||
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())
|
||||
Method = cast<CXXMethodDecl>(InstantiatedMethod);
|
||||
// FIXME(kirillbobyrev): For virtual methods with
|
||||
// size_overridden_methods() > 1, this will not rename all functions it
|
||||
// overrides, because this code assumes there is a single canonical
|
||||
// declaration.
|
||||
while (Method->isVirtual() && Method->size_overridden_methods())
|
||||
Method = *Method->overridden_methods().begin();
|
||||
return dyn_cast<NamedDecl>(Method->getCanonicalDecl());
|
||||
}
|
||||
if (const auto *Function = dyn_cast<FunctionDecl>(D))
|
||||
if (const FunctionTemplateDecl *Template = Function->getPrimaryTemplate())
|
||||
return canonicalRenameDecl(Template);
|
||||
return dyn_cast<NamedDecl>(D->getCanonicalDecl());
|
||||
}
|
||||
|
||||
llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
|
||||
SourceLocation TokenStartLoc) {
|
||||
unsigned Offset =
|
||||
|
@ -91,9 +142,7 @@ llvm::DenseSet<const NamedDecl *> locateDeclAt(ParsedAST &AST,
|
|||
for (const NamedDecl *D :
|
||||
targetDecl(SelectedNode->ASTNode,
|
||||
DeclRelation::Alias | DeclRelation::TemplatePattern)) {
|
||||
// Get to CXXRecordDecl from constructor or destructor.
|
||||
D = tooling::getCanonicalSymbolDeclaration(D);
|
||||
Result.insert(D);
|
||||
Result.insert(canonicalRenameDecl(D));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
@ -226,19 +275,8 @@ llvm::Error makeError(ReasonToReject Reason) {
|
|||
std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
|
||||
const NamedDecl &ND) {
|
||||
trace::Span Tracer("FindOccurrencesWithinFile");
|
||||
// If the cursor is at the underlying CXXRecordDecl of the
|
||||
// ClassTemplateDecl, ND will be the CXXRecordDecl. In this case, we need to
|
||||
// get the primary template manually.
|
||||
// getUSRsForDeclaration will find other related symbols, e.g. virtual and its
|
||||
// overriddens, primary template and all explicit specializations.
|
||||
// FIXME: Get rid of the remaining tooling APIs.
|
||||
const auto *RenameDecl =
|
||||
ND.getDescribedTemplate() ? ND.getDescribedTemplate() : &ND;
|
||||
std::vector<std::string> RenameUSRs =
|
||||
tooling::getUSRsForDeclaration(RenameDecl, AST.getASTContext());
|
||||
llvm::DenseSet<SymbolID> TargetIDs;
|
||||
for (auto &USR : RenameUSRs)
|
||||
TargetIDs.insert(SymbolID(USR));
|
||||
assert(canonicalRenameDecl(&ND) == &ND &&
|
||||
"ND should be already canonicalized.");
|
||||
|
||||
std::vector<SourceLocation> Results;
|
||||
for (Decl *TopLevelDecl : AST.getLocalTopLevelDecls()) {
|
||||
|
@ -246,11 +284,11 @@ std::vector<SourceLocation> findOccurrencesWithinFile(ParsedAST &AST,
|
|||
if (Ref.Targets.empty())
|
||||
return;
|
||||
for (const auto *Target : Ref.Targets) {
|
||||
auto ID = getSymbolID(Target);
|
||||
if (!ID || TargetIDs.find(ID) == TargetIDs.end())
|
||||
if (canonicalRenameDecl(Target) == &ND) {
|
||||
Results.push_back(Ref.NameLoc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Results.push_back(Ref.NameLoc);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -557,8 +595,7 @@ llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
|
|||
return makeError(ReasonToReject::NoSymbolFound);
|
||||
if (DeclsUnderCursor.size() > 1)
|
||||
return makeError(ReasonToReject::AmbiguousSymbol);
|
||||
const auto &RenameDecl =
|
||||
llvm::cast<NamedDecl>(*(*DeclsUnderCursor.begin())->getCanonicalDecl());
|
||||
const auto &RenameDecl = **DeclsUnderCursor.begin();
|
||||
if (RenameDecl.getName() == RInputs.NewName)
|
||||
return makeError(ReasonToReject::SameName);
|
||||
auto Invalid = checkName(RenameDecl, RInputs.NewName);
|
||||
|
|
Loading…
Reference in New Issue