[clangd] A helper to find explicit references and their names

Summary:
Allows to simplify pending code tweaks:
  - the upcoming DefineInline tweak (D66647)
  - remove using declaration (D56612)
  - qualify name under cursor (D56610)

Another potential future application is simplifying semantic highlighting.

Reviewers: kadircet

Reviewed By: kadircet

Subscribers: mgrang, jfb, MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D67826

llvm-svn: 372859
This commit is contained in:
Ilya Biryukov 2019-09-25 12:40:22 +00:00
parent 823fd9508a
commit f96d2e1754
3 changed files with 549 additions and 1 deletions

View File

@ -10,22 +10,33 @@
#include "AST.h"
#include "Logger.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclVisitor.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/AST/TypeLocVisitor.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/raw_ostream.h"
#include <utility>
namespace clang {
namespace clangd {
namespace {
using ast_type_traits::DynTypedNode;
LLVM_ATTRIBUTE_UNUSED std::string
nodeToString(const ast_type_traits::DynTypedNode &N) {
@ -348,12 +359,274 @@ allTargetDecls(const ast_type_traits::DynTypedNode &N) {
llvm::SmallVector<const Decl *, 1>
targetDecl(const ast_type_traits::DynTypedNode &N, DeclRelationSet Mask) {
llvm::SmallVector<const Decl *, 1> Result;
for (const auto &Entry : allTargetDecls(N))
for (const auto &Entry : allTargetDecls(N)) {
if (!(Entry.second & ~Mask))
Result.push_back(Entry.first);
}
return Result;
}
namespace {
/// Find declarations explicitly referenced in the source code defined by \p N.
/// For templates, will prefer to return a template instantiation whenever
/// possible. However, can also return a template pattern if the specialization
/// cannot be picked, e.g. in dependent code or when there is no corresponding
/// Decl for a template instantitation, e.g. for templated using decls:
/// template <class T> using Ptr = T*;
/// Ptr<int> x;
/// ^~~ there is no Decl for 'Ptr<int>', so we return the template pattern.
llvm::SmallVector<const NamedDecl *, 1>
explicitReferenceTargets(DynTypedNode N, DeclRelationSet Mask = {}) {
assert(!(Mask & (DeclRelation::TemplatePattern |
DeclRelation::TemplateInstantiation)) &&
"explicitRefenceTargets handles templates on its own");
auto Decls = allTargetDecls(N);
// We prefer to return template instantiation, but fallback to template
// pattern if instantiation is not available.
Mask |= DeclRelation::TemplatePattern | DeclRelation::TemplateInstantiation;
llvm::SmallVector<const NamedDecl *, 1> TemplatePatterns;
llvm::SmallVector<const NamedDecl *, 1> Targets;
bool SeenTemplateInstantiations = false;
for (auto &D : Decls) {
if (D.second & ~Mask)
continue;
if (D.second & DeclRelation::TemplatePattern) {
TemplatePatterns.push_back(llvm::cast<NamedDecl>(D.first));
continue;
}
if (D.second & DeclRelation::TemplateInstantiation)
SeenTemplateInstantiations = true;
Targets.push_back(llvm::cast<NamedDecl>(D.first));
}
if (!SeenTemplateInstantiations)
Targets.insert(Targets.end(), TemplatePatterns.begin(),
TemplatePatterns.end());
return Targets;
}
Optional<ReferenceLoc> refInDecl(const Decl *D) {
struct Visitor : ConstDeclVisitor<Visitor> {
llvm::Optional<ReferenceLoc> Ref;
void VisitUsingDirectiveDecl(const UsingDirectiveDecl *D) {
Ref = ReferenceLoc{D->getQualifierLoc(),
D->getIdentLocation(),
{D->getNominatedNamespaceAsWritten()}};
}
void VisitUsingDecl(const UsingDecl *D) {
Ref = ReferenceLoc{D->getQualifierLoc(), D->getLocation(),
explicitReferenceTargets(DynTypedNode::create(*D),
DeclRelation::Underlying)};
}
void VisitNamespaceAliasDecl(const NamespaceAliasDecl *D) {
Ref = ReferenceLoc{D->getQualifierLoc(),
D->getTargetNameLoc(),
{D->getAliasedNamespace()}};
}
};
Visitor V;
V.Visit(D);
return V.Ref;
}
Optional<ReferenceLoc> refInExpr(const Expr *E) {
struct Visitor : ConstStmtVisitor<Visitor> {
// FIXME: handle more complicated cases, e.g. ObjC, designated initializers.
llvm::Optional<ReferenceLoc> Ref;
void VisitDeclRefExpr(const DeclRefExpr *E) {
Ref = ReferenceLoc{
E->getQualifierLoc(), E->getNameInfo().getLoc(), {E->getFoundDecl()}};
}
void VisitMemberExpr(const MemberExpr *E) {
Ref = ReferenceLoc{E->getQualifierLoc(),
E->getMemberNameInfo().getLoc(),
{E->getFoundDecl()}};
}
};
Visitor V;
V.Visit(E);
return V.Ref;
}
Optional<ReferenceLoc> refInTypeLoc(TypeLoc L) {
struct Visitor : TypeLocVisitor<Visitor> {
llvm::Optional<ReferenceLoc> Ref;
void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) {
// We only know about qualifier, rest if filled by inner locations.
Visit(L.getNamedTypeLoc().getUnqualifiedLoc());
// Fill in the qualifier.
if (!Ref)
return;
assert(!Ref->Qualifier.hasQualifier() && "qualifier already set");
Ref->Qualifier = L.getQualifierLoc();
}
void VisitDeducedTemplateSpecializationTypeLoc(
DeducedTemplateSpecializationTypeLoc L) {
Ref = ReferenceLoc{
NestedNameSpecifierLoc(), L.getNameLoc(),
explicitReferenceTargets(DynTypedNode::create(L.getType()))};
}
void VisitTagTypeLoc(TagTypeLoc L) {
Ref =
ReferenceLoc{NestedNameSpecifierLoc(), L.getNameLoc(), {L.getDecl()}};
}
void VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc L) {
Ref = ReferenceLoc{
NestedNameSpecifierLoc(), L.getTemplateNameLoc(),
explicitReferenceTargets(DynTypedNode::create(L.getType()))};
}
void VisitDependentTemplateSpecializationTypeLoc(
DependentTemplateSpecializationTypeLoc L) {
Ref = ReferenceLoc{
L.getQualifierLoc(), L.getTemplateNameLoc(),
explicitReferenceTargets(DynTypedNode::create(L.getType()))};
}
void VisitDependentNameTypeLoc(DependentNameTypeLoc L) {
Ref = ReferenceLoc{
L.getQualifierLoc(), L.getNameLoc(),
explicitReferenceTargets(DynTypedNode::create(L.getType()))};
}
void VisitTypedefTypeLoc(TypedefTypeLoc L) {
Ref = ReferenceLoc{
NestedNameSpecifierLoc(), L.getNameLoc(), {L.getTypedefNameDecl()}};
}
};
Visitor V;
V.Visit(L.getUnqualifiedLoc());
return V.Ref;
}
class ExplicitReferenceColletor
: public RecursiveASTVisitor<ExplicitReferenceColletor> {
public:
ExplicitReferenceColletor(llvm::function_ref<void(ReferenceLoc)> Out)
: Out(Out) {
assert(Out);
}
bool VisitTypeLoc(TypeLoc TTL) {
if (TypeLocsToSkip.count(TTL.getBeginLoc().getRawEncoding()))
return true;
visitNode(DynTypedNode::create(TTL));
return true;
}
bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc L) {
// ElaboratedTypeLoc will reports information for its inner type loc.
// Otherwise we loose information about inner types loc's qualifier.
TypeLoc Inner = L.getNamedTypeLoc().getUnqualifiedLoc();
TypeLocsToSkip.insert(Inner.getBeginLoc().getRawEncoding());
return RecursiveASTVisitor::TraverseElaboratedTypeLoc(L);
}
bool VisitExpr(Expr *E) {
visitNode(DynTypedNode::create(*E));
return true;
}
bool VisitDecl(Decl *D) {
visitNode(DynTypedNode::create(*D));
return true;
}
// We have to use Traverse* because there is no corresponding Visit*.
bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc L) {
if (!L.getNestedNameSpecifier())
return true;
visitNode(DynTypedNode::create(L));
// Inner type is missing information about its qualifier, skip it.
if (auto TL = L.getTypeLoc())
TypeLocsToSkip.insert(TL.getBeginLoc().getRawEncoding());
return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L);
}
private:
/// Obtain information about a reference directly defined in \p N. Does not
/// recurse into child nodes, e.g. do not expect references for constructor
/// initializers
///
/// Any of the fields in the returned structure can be empty, but not all of
/// them, e.g.
/// - for implicitly generated nodes (e.g. MemberExpr from range-based-for),
/// source location information may be missing,
/// - for dependent code, targets may be empty.
///
/// (!) For the purposes of this function declarations are not considered to
/// be references. However, declarations can have references inside them,
/// e.g. 'namespace foo = std' references namespace 'std' and this
/// function will return the corresponding reference.
llvm::Optional<ReferenceLoc> explicitReference(DynTypedNode N) {
if (auto *D = N.get<Decl>())
return refInDecl(D);
if (auto *E = N.get<Expr>())
return refInExpr(E);
if (auto *NNSL = N.get<NestedNameSpecifierLoc>())
return ReferenceLoc{NNSL->getPrefix(), NNSL->getLocalBeginLoc(),
explicitReferenceTargets(DynTypedNode::create(
*NNSL->getNestedNameSpecifier()))};
if (const TypeLoc *TL = N.get<TypeLoc>())
return refInTypeLoc(*TL);
if (const CXXCtorInitializer *CCI = N.get<CXXCtorInitializer>()) {
if (CCI->isBaseInitializer())
return refInTypeLoc(CCI->getBaseClassLoc());
assert(CCI->isAnyMemberInitializer());
return ReferenceLoc{NestedNameSpecifierLoc(),
CCI->getMemberLocation(),
{CCI->getAnyMember()}};
}
// We do not have location information for other nodes (QualType, etc)
return llvm::None;
}
void visitNode(DynTypedNode N) {
auto Ref = explicitReference(N);
if (!Ref)
return;
// Our promise is to return only references from the source code. If we lack
// location information, skip these nodes.
// Normally this should not happen in practice, unless there are bugs in the
// traversals or users started the traversal at an implicit node.
if (Ref->NameLoc.isInvalid()) {
dlog("invalid location at node {0}", nodeToString(N));
return;
}
Out(*Ref);
}
llvm::function_ref<void(ReferenceLoc)> Out;
/// TypeLocs starting at these locations must be skipped, see
/// TraverseElaboratedTypeSpecifierLoc for details.
llvm::DenseSet</*SourceLocation*/ unsigned> TypeLocsToSkip;
};
} // namespace
void findExplicitReferences(Stmt *S,
llvm::function_ref<void(ReferenceLoc)> Out) {
assert(S);
ExplicitReferenceColletor(Out).TraverseStmt(S);
}
void findExplicitReferences(Decl *D,
llvm::function_ref<void(ReferenceLoc)> Out) {
assert(D);
ExplicitReferenceColletor(Out).TraverseDecl(D);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, DeclRelation R) {
switch (R) {
#define REL_CASE(X) \
@ -378,5 +651,26 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, DeclRelationSet RS) {
return OS;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ReferenceLoc R) {
// note we cannot print R.NameLoc without a source manager.
OS << "targets = {";
bool First = true;
for (const NamedDecl *T : R.Targets) {
if (!First)
OS << ", ";
else
First = false;
OS << printQualifiedName(*T) << printTemplateSpecializationArgs(*T);
}
OS << "}";
if (R.Qualifier) {
OS << ", qualifier = '";
R.Qualifier.getNestedNameSpecifier()->print(OS,
PrintingPolicy(LangOptions()));
OS << "'";
}
return OS;
}
} // namespace clangd
} // namespace clang

View File

@ -20,8 +20,14 @@
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/raw_ostream.h"
#include <bitset>
@ -69,6 +75,34 @@ class DeclRelationSet;
llvm::SmallVector<const Decl *, 1>
targetDecl(const ast_type_traits::DynTypedNode &, DeclRelationSet Mask);
/// Information about a reference written in the source code, independent of the
/// actual AST node that this reference lives in.
/// Useful for tools that are source-aware, e.g. refactorings.
struct ReferenceLoc {
/// Contains qualifier written in the code, if any, e.g. 'ns::' for 'ns::foo'.
NestedNameSpecifierLoc Qualifier;
/// Start location of the last name part, i.e. 'foo' in 'ns::foo<int>'.
SourceLocation NameLoc;
// FIXME: add info about template arguments.
/// A list of targets referenced by this name. Normally this has a single
/// element, but multiple is also possible, e.g. in case of using declarations
/// or unresolved overloaded functions.
/// For dependent and unresolved references, Targets can also be empty.
llvm::SmallVector<const NamedDecl *, 1> Targets;
};
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ReferenceLoc R);
/// Recursively traverse \p S and report all references explicitly written in
/// the code. The main use-case is refactorings that need to process all
/// references in some subrange of the file and apply simple edits, e.g. add
/// qualifiers.
/// FIXME: currently this does not report references to overloaded operators.
/// FIXME: extend to report location information about declaration names too.
void findExplicitReferences(Stmt *S,
llvm::function_ref<void(ReferenceLoc)> Out);
void findExplicitReferences(Decl *D,
llvm::function_ref<void(ReferenceLoc)> Out);
/// Similar to targetDecl(), however instead of applying a filter, all possible
/// decls are returned along with their DeclRelationSets.
/// This is suitable for indexing, where everything is recorded and filtering

View File

@ -9,6 +9,9 @@
#include "Selection.h"
#include "TestTU.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/Annotations.h"
#include "gmock/gmock.h"
@ -464,6 +467,223 @@ TEST_F(TargetDeclTest, ObjC) {
EXPECT_DECLS("ObjCObjectTypeLoc");
}
class FindExplicitReferencesTest : public ::testing::Test {
protected:
struct AllRefs {
std::string AnnotatedCode;
std::string DumpedReferences;
};
/// Parses \p Code, finds function '::foo' and annotates its body with results
/// of findExplicitReferecnces.
/// See actual tests for examples of annotation format.
AllRefs annotateReferencesInFoo(llvm::StringRef Code) {
TestTU TU;
TU.Code = Code;
auto AST = TU.build();
auto &Func = llvm::cast<FunctionDecl>(findDecl(AST, "foo"));
std::vector<ReferenceLoc> Refs;
findExplicitReferences(Func.getBody(), [&Refs](ReferenceLoc R) {
Refs.push_back(std::move(R));
});
auto &SM = AST.getSourceManager();
llvm::sort(Refs, [&](const ReferenceLoc &L, const ReferenceLoc &R) {
return SM.isBeforeInTranslationUnit(L.NameLoc, R.NameLoc);
});
std::string AnnotatedCode;
unsigned NextCodeChar = 0;
for (unsigned I = 0; I < Refs.size(); ++I) {
auto &R = Refs[I];
SourceLocation Pos = R.NameLoc;
assert(Pos.isValid());
if (Pos.isMacroID()) // FIXME: figure out how to show macro locations.
Pos = SM.getExpansionLoc(Pos);
assert(Pos.isFileID());
FileID File;
unsigned Offset;
std::tie(File, Offset) = SM.getDecomposedLoc(Pos);
if (File == SM.getMainFileID()) {
// Print the reference in a source code.
assert(NextCodeChar <= Offset);
AnnotatedCode += Code.substr(NextCodeChar, Offset - NextCodeChar);
AnnotatedCode += "$" + std::to_string(I) + "^";
NextCodeChar = Offset;
}
}
AnnotatedCode += Code.substr(NextCodeChar);
std::string DumpedReferences;
for (unsigned I = 0; I < Refs.size(); ++I)
DumpedReferences += llvm::formatv("{0}: {1}\n", I, Refs[I]);
return AllRefs{std::move(AnnotatedCode), std::move(DumpedReferences)};
}
};
TEST_F(FindExplicitReferencesTest, All) {
std::pair</*Code*/ llvm::StringRef, /*References*/ llvm::StringRef> Cases[] =
{
// Simple expressions.
{R"cpp(
int global;
int func();
void foo(int param) {
$0^global = $1^param + $2^func();
}
)cpp",
"0: targets = {global}\n"
"1: targets = {param}\n"
"2: targets = {func}\n"},
{R"cpp(
struct X { int a; };
void foo(X x) {
$0^x.$1^a = 10;
}
)cpp",
"0: targets = {x}\n"
"1: targets = {X::a}\n"},
// Namespaces and aliases.
{R"cpp(
namespace ns {}
namespace alias = ns;
void foo() {
using namespace $0^ns;
using namespace $1^alias;
}
)cpp",
"0: targets = {ns}\n"
"1: targets = {alias}\n"},
// Using declarations.
{R"cpp(
namespace ns { int global; }
void foo() {
using $0^ns::$1^global;
}
)cpp",
"0: targets = {ns}\n"
"1: targets = {ns::global}, qualifier = 'ns::'\n"},
// Simple types.
{R"cpp(
struct Struct { int a; };
using Typedef = int;
void foo() {
$0^Struct x;
$1^Typedef y;
static_cast<$2^Struct*>(0);
}
)cpp",
"0: targets = {Struct}\n"
"1: targets = {Typedef}\n"
"2: targets = {Struct}\n"},
// Name qualifiers.
{R"cpp(
namespace a { namespace b { struct S { typedef int type; }; } }
void foo() {
$0^a::$1^b::$2^S x;
using namespace $3^a::$4^b;
$5^S::$6^type y;
}
)cpp",
"0: targets = {a}\n"
"1: targets = {a::b}, qualifier = 'a::'\n"
"2: targets = {a::b::S}, qualifier = 'a::b::'\n"
"3: targets = {a}\n"
"4: targets = {a::b}, qualifier = 'a::'\n"
"5: targets = {a::b::S}\n"
"6: targets = {a::b::S::type}, qualifier = 'struct S::'\n"},
// Simple templates.
{R"cpp(
template <class T> struct vector { using value_type = T; };
template <> struct vector<bool> { using value_type = bool; };
void foo() {
$0^vector<int> vi;
$1^vector<bool> vb;
}
)cpp",
"0: targets = {vector<int>}\n"
"1: targets = {vector<bool>}\n"},
// FIXME: Fix 'allTargetDecls' to return alias template and re-enable.
// Template type aliases.
// {R"cpp(
// template <class T> struct vector { using value_type = T; };
// template <> struct vector<bool> { using value_type = bool; };
// template <class T> using valias = vector<T>;
// void foo() {
// $0^valias<int> vi;
// $1^valias<bool> vb;
// }
// )cpp",
// "0: targets = {valias}\n"
// "1: targets = {valias}\n"},
// MemberExpr should know their using declaration.
{R"cpp(
struct X { void func(int); }
struct Y : X {
using X::func;
};
void foo(Y y) {
$0^y.$1^func(1);
}
)cpp",
"0: targets = {y}\n"
"1: targets = {Y::func}\n"},
// DeclRefExpr should know their using declaration.
{R"cpp(
namespace ns { void bar(int); }
using ns::bar;
void foo() {
$0^bar(10);
}
)cpp",
"0: targets = {bar}\n"},
// References from a macro.
{R"cpp(
#define FOO a
#define BAR b
void foo(int a, int b) {
$0^FOO+$1^BAR;
}
)cpp",
"0: targets = {a}\n"
"1: targets = {b}\n"},
// No references from implicit nodes.
{R"cpp(
struct vector {
int *begin();
int *end();
};
void foo() {
for (int x : $0^vector()) {
$1^x = 10;
}
}
)cpp",
"0: targets = {vector}\n"
"1: targets = {x}\n"},
};
for (const auto &C : Cases) {
llvm::StringRef ExpectedCode = C.first;
llvm::StringRef ExpectedRefs = C.second;
auto Actual =
annotateReferencesInFoo(llvm::Annotations(ExpectedCode).code());
EXPECT_EQ(ExpectedCode, Actual.AnnotatedCode);
EXPECT_EQ(ExpectedRefs, Actual.DumpedReferences) << ExpectedCode;
}
}
} // namespace
} // namespace clangd
} // namespace clang