[clangd] Support ConceptReference in generic AST wrangling code

Now we can store it in DynTypedNode, we can target these nodes
(SelectionTree) and resolve them (FindTarget).
This makes Hover, go-to-def etc work in all(?) cases.

Also support it in DumpAST.

Differential Revision: https://reviews.llvm.org/D159299
This commit is contained in:
Sam McCall 2023-08-31 20:14:35 +02:00
parent fae3f9ec4f
commit a8c9b9f140
6 changed files with 102 additions and 13 deletions

View File

@ -205,6 +205,11 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
// To avoid special cases in the API/UI, use public/private as the kind.
return getAccessSpelling(CBS.getAccessSpecifier()).str();
}
std::string getKind(const ConceptReference *CR) {
// Again there are no variants here.
// Kind is "Concept", role is "reference"
return "Concept";
}
// Detail is the single most important fact about the node.
// Often this is the name, sometimes a "kind" enum like operators or casts.
@ -305,6 +310,9 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
std::string getDetail(const CXXBaseSpecifier &CBS) {
return CBS.isVirtual() ? "virtual" : "";
}
std::string getDetail(const ConceptReference *CR) {
return CR->getNamedConcept()->getNameAsString();
}
/// Arcana is produced by TextNodeDumper, for the types it supports.
@ -365,6 +373,10 @@ public:
bool TraverseAttr(Attr *A) {
return !A || traverseNode("attribute", A, [&] { Base::TraverseAttr(A); });
}
bool TraverseConceptReference(ConceptReference *C) {
return !C || traverseNode("reference", C,
[&] { Base::TraverseConceptReference(C); });
}
bool TraverseCXXBaseSpecifier(const CXXBaseSpecifier &CBS) {
return traverseNode("base", CBS,
[&] { Base::TraverseCXXBaseSpecifier(CBS); });
@ -422,6 +434,8 @@ ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens,
V.TraverseTemplateArgumentLoc(*const_cast<TemplateArgumentLoc *>(TAL));
else if (const auto *CBS = N.get<CXXBaseSpecifier>())
V.TraverseCXXBaseSpecifier(*const_cast<CXXBaseSpecifier *>(CBS));
else if (const auto *CR = N.get<ConceptReference>())
V.TraverseConceptReference(const_cast<ConceptReference *>(CR));
else
elog("dumpAST: unhandled DynTypedNode kind {0}",
N.getNodeKind().asStringRef());

View File

@ -10,6 +10,7 @@
#include "AST.h"
#include "HeuristicResolver.h"
#include "support/Logger.h"
#include "clang/AST/ASTConcept.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
@ -257,7 +258,7 @@ public:
Outer.add(CE->getCalleeDecl(), Flags);
}
void VisitConceptSpecializationExpr(const ConceptSpecializationExpr *E) {
Outer.add(E->getNamedConcept(), Flags);
Outer.add(E->getConceptReference(), Flags);
}
void VisitDeclRefExpr(const DeclRefExpr *DRE) {
const Decl *D = DRE->getDecl();
@ -532,6 +533,10 @@ public:
add(USD, Flags);
}
}
void add(const ConceptReference *CR, RelSet Flags) {
add(CR->getNamedConcept(), Flags);
}
};
} // namespace
@ -561,6 +566,8 @@ allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) {
Finder.add(CBS->getTypeSourceInfo()->getType(), Flags);
else if (const ObjCProtocolLoc *PL = N.get<ObjCProtocolLoc>())
Finder.add(PL->getProtocol(), Flags);
else if (const ConceptReference *CR = N.get<ConceptReference>())
Finder.add(CR, Flags);
return Finder.takeDecls();
}
@ -1056,11 +1063,8 @@ public:
return RecursiveASTVisitor::TraverseConstructorInitializer(Init);
}
bool VisitConceptReference(ConceptReference *ConceptRef) {
Out(ReferenceLoc{ConceptRef->getNestedNameSpecifierLoc(),
ConceptRef->getConceptNameLoc(),
/*IsDecl=*/false,
{ConceptRef->getNamedConcept()}});
bool VisitConceptReference(const ConceptReference *CR) {
visitNode(DynTypedNode::create(*CR));
return true;
}
@ -1109,6 +1113,11 @@ private:
PL->getLocation(),
/*IsDecl=*/false,
{PL->getProtocol()}}};
if (const ConceptReference *CR = N.get<ConceptReference>())
return {ReferenceLoc{CR->getNestedNameSpecifierLoc(),
CR->getConceptNameLoc(),
/*IsDecl=*/false,
{CR->getNamedConcept()}}};
// We do not have location information for other nodes (QualType, etc)
return {};

View File

@ -635,8 +635,12 @@ public:
if (llvm::isa_and_nonnull<TranslationUnitDecl>(X))
return Base::TraverseDecl(X); // Already pushed by constructor.
// Base::TraverseDecl will suppress children, but not this node itself.
if (X && X->isImplicit())
return true;
if (X && X->isImplicit()) {
// Most implicit nodes have only implicit children and can be skipped.
// However there are exceptions (`void foo(Concept auto x)`), and
// the base implementation knows how to find them.
return Base::TraverseDecl(X);
}
return traverseNode(X, [&] { return Base::TraverseDecl(X); });
}
bool TraverseTypeLoc(TypeLoc X) {
@ -660,6 +664,9 @@ public:
bool TraverseAttr(Attr *X) {
return traverseNode(X, [&] { return Base::TraverseAttr(X); });
}
bool TraverseConceptReference(ConceptReference *X) {
return traverseNode(X, [&] { return Base::TraverseConceptReference(X); });
}
// Stmt is the same, but this form allows the data recursion optimization.
bool dataTraverseStmtPre(Stmt *X) {
if (!X || isImplicit(X))

View File

@ -537,7 +537,7 @@ TEST_F(TargetDeclTest, Concept) {
}
)cpp";
EXPECT_DECLS(
"ConceptSpecializationExpr",
"ConceptReference",
{"template <typename T> concept Fooable = requires (T t) { t.foo(); }"});
// trailing requires clause
@ -548,7 +548,7 @@ TEST_F(TargetDeclTest, Concept) {
template <typename T>
void foo() requires [[Fooable]]<T>;
)cpp";
EXPECT_DECLS("ConceptSpecializationExpr",
EXPECT_DECLS("ConceptReference",
{"template <typename T> concept Fooable = true"});
// constrained-parameter
@ -559,7 +559,7 @@ TEST_F(TargetDeclTest, Concept) {
template <[[Fooable]] T>
void bar(T t);
)cpp";
EXPECT_DECLS("ConceptSpecializationExpr",
EXPECT_DECLS("ConceptReference",
{"template <typename T> concept Fooable = true"});
// partial-concept-id
@ -570,7 +570,7 @@ TEST_F(TargetDeclTest, Concept) {
template <[[Fooable]]<int> T>
void bar(T t);
)cpp";
EXPECT_DECLS("ConceptSpecializationExpr",
EXPECT_DECLS("ConceptReference",
{"template <typename T, typename U> concept Fooable = true"});
}

View File

@ -487,6 +487,16 @@ class Foo final {})cpp";
HI.Kind = index::SymbolKind::TypeAlias;
HI.Definition = "int";
}},
{R"cpp(
template <class T> concept F = true;
[[^F]] auto x = 1;
)cpp",
[](HoverInfo &HI) {
HI.NamespaceScope = "";
HI.Name = "F";
HI.Kind = index::SymbolKind::Concept;
HI.Definition = "template <class T>\nconcept F = true";
}},
// auto on lambda
{R"cpp(
void foo() {
@ -535,7 +545,7 @@ class Foo final {})cpp";
HI.Kind = index::SymbolKind::Concept;
HI.Definition = "template <class T>\nconcept Fooable = true";
}},
{R"cpp(
{R"cpp(
template<class T> concept Fooable = true;
template<Fooable [[T^T]]>
void bar(TT t) {}
@ -549,6 +559,28 @@ class Foo final {})cpp";
HI.Kind = index::SymbolKind::TemplateTypeParm;
HI.Definition = "Fooable TT";
}},
{R"cpp(
template<class T> concept Fooable = true;
void bar([[Foo^able]] auto t) {}
)cpp",
[](HoverInfo &HI) {
HI.NamespaceScope = "";
HI.Name = "Fooable";
HI.Kind = index::SymbolKind::Concept;
HI.Definition = "template <class T>\nconcept Fooable = true";
}},
// concept reference
{R"cpp(
template<class T> concept Fooable = true;
auto X = [[Fooa^ble]]<int>;
)cpp",
[](HoverInfo &HI) {
HI.NamespaceScope = "";
HI.Name = "Fooable";
HI.Kind = index::SymbolKind::Concept;
HI.Definition = "template <class T>\nconcept Fooable = true";
HI.Value = "true";
}},
// empty macro
{R"cpp(

View File

@ -561,6 +561,33 @@ TEST(SelectionTest, CommonAncestor) {
[[^using enum ns::A]];
)cpp",
"UsingEnumDecl"},
// concepts
{R"cpp(
template <class> concept C = true;
auto x = [[^C<int>]];
)cpp",
"ConceptReference"},
{R"cpp(
template <class> concept C = true;
[[^C]] auto x = 0;
)cpp",
"ConceptReference"},
{R"cpp(
template <class> concept C = true;
void foo([[^C]] auto x) {}
)cpp",
"ConceptReference"},
{R"cpp(
template <class> concept C = true;
template <[[^C]] x> int i = 0;
)cpp",
"ConceptReference"},
{R"cpp(
namespace ns { template <class> concept C = true; }
auto x = [[ns::^C<int>]];
)cpp",
"ConceptReference"},
};
for (const Case &C : Cases) {