Skip to content

Commit cb05da3

Browse files
ilya-biryukovhyp
authored andcommitted
[Index] [clangd] Support for concept declarations and requires expressions
Add support for concepts and requires expression in the clang index. Genarate USRs for concepts. Also change how `RecursiveASTVisitor` handles return type requirement in requires expressions. The new code unpacks the synthetic template parameter list used for storing the actual expression. This simplifies implementation of the indexing. No code seems to depend on the original traversal anyway and the synthesized template parameter list is easily accessible from inside the requires expression if needed. Add tests in the clangd codebase. Fixes clangd/clangd#1103. Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D124441
1 parent 1a0ab67 commit cb05da3

18 files changed

+185
-33
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
131131
case SK::TemplateTypeParm:
132132
case SK::TemplateTemplateParm:
133133
return CompletionItemKind::TypeParameter;
134+
case SK::Concept:
135+
return CompletionItemKind::Interface;
134136
}
135137
llvm_unreachable("Unhandled clang::index::SymbolKind.");
136138
}

clang-tools-extra/clangd/Protocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,8 @@ SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
305305
case index::SymbolKind::TemplateTemplateParm:
306306
case index::SymbolKind::TemplateTypeParm:
307307
return SymbolKind::TypeParameter;
308+
case index::SymbolKind::Concept:
309+
return SymbolKind::Interface;
308310
}
309311
llvm_unreachable("invalid symbol kind");
310312
}

clang-tools-extra/clangd/Quality.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ categorize(const index::SymbolInfo &D) {
132132
case index::SymbolKind::TypeAlias:
133133
case index::SymbolKind::TemplateTypeParm:
134134
case index::SymbolKind::TemplateTemplateParm:
135+
case index::SymbolKind::Concept:
135136
return SymbolQualitySignals::Type;
136137
case index::SymbolKind::Function:
137138
case index::SymbolKind::ClassMethod:

clang-tools-extra/clangd/index/SymbolCollector.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ bool shouldCollectIncludePath(index::SymbolKind Kind) {
9191
case SK::Function:
9292
case SK::Variable:
9393
case SK::EnumConstant:
94+
case SK::Concept:
9495
return true;
9596
default:
9697
return false;

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3308,6 +3308,38 @@ TEST(CompletionTest, CommentParamName) {
33083308
IsEmpty());
33093309
}
33103310

3311+
TEST(CompletionTest, Concepts) {
3312+
Annotations Code(R"cpp(
3313+
template<class T>
3314+
concept A = sizeof(T) <= 8;
3315+
3316+
template<$tparam^A U>
3317+
int foo();
3318+
3319+
template<class T>
3320+
concept b = $other^A<T> && $other^sizeof(T) % 2 == 0 || $other^A<T> && sizeof(T) == 1;
3321+
3322+
$other^A<T> auto i = 19;
3323+
)cpp");
3324+
TestTU TU;
3325+
TU.Code = Code.code().str();
3326+
TU.ExtraArgs = {"-std=c++20"};
3327+
3328+
std::vector<Symbol> Syms = {conceptSym("same_as")};
3329+
for (auto P : Code.points("tparam")) {
3330+
ASSERT_THAT(completions(TU, P, Syms).Completions,
3331+
AllOf(Contains(named("A")), Contains(named("same_as")),
3332+
Contains(named("class")), Contains(named("typename"))))
3333+
<< "Completing template parameter at position " << P;
3334+
}
3335+
3336+
for (auto P : Code.points("other")) {
3337+
EXPECT_THAT(completions(TU, P, Syms).Completions,
3338+
AllOf(Contains(named("A")), Contains(named("same_as"))))
3339+
<< "Completing 'requires' expression at position " << P;
3340+
}
3341+
}
3342+
33113343
} // namespace
33123344
} // namespace clangd
33133345
} // namespace clang

clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ MATCHER_P(HasName, Name, "") { return arg.Name == Name; }
5959
MATCHER_P(TemplateArgs, TemplArgs, "") {
6060
return arg.TemplateSpecializationArgs == TemplArgs;
6161
}
62+
MATCHER_P(hasKind, Kind, "") { return arg.SymInfo.Kind == Kind; }
6263
MATCHER_P(DeclURI, P, "") {
6364
return StringRef(arg.CanonicalDeclaration.FileURI) == P;
6465
}
@@ -1849,6 +1850,17 @@ TEST_F(SymbolCollectorTest, NoCrashOnObjCMethodCStyleParam) {
18491850
UnorderedElementsAre(QName("Foo"), QName("Foo::fun:")));
18501851
}
18511852

1853+
TEST_F(SymbolCollectorTest, Concepts) {
1854+
const char *Header = R"cpp(
1855+
template <class T>
1856+
concept A = sizeof(T) <= 8;
1857+
)cpp";
1858+
runSymbolCollector("", Header, {"-std=c++20"});
1859+
EXPECT_THAT(Symbols,
1860+
UnorderedElementsAre(AllOf(
1861+
qName("A"), hasKind(clang::index::SymbolKind::Concept))));
1862+
}
1863+
18521864
} // namespace
18531865
} // namespace clangd
18541866
} // namespace clang

clang-tools-extra/clangd/unittests/TestIndex.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ Symbol ns(llvm::StringRef Name) {
7373
return sym(Name, index::SymbolKind::Namespace, "@N@\\0");
7474
}
7575

76+
Symbol conceptSym(llvm::StringRef Name) {
77+
return sym(Name, index::SymbolKind::Concept, "@CT@\\0");
78+
}
79+
7680
SymbolSlab generateSymbols(std::vector<std::string> QualifiedNames) {
7781
SymbolSlab::Builder Slab;
7882
for (llvm::StringRef QName : QualifiedNames)

clang-tools-extra/clangd/unittests/TestIndex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Symbol cls(llvm::StringRef Name);
3030
Symbol var(llvm::StringRef Name);
3131
// Creates a namespace symbol.
3232
Symbol ns(llvm::StringRef Name);
33+
// Create a C++20 concept symbol.
34+
Symbol conceptSym(llvm::StringRef Name);
3335

3436
// Create a slab of symbols with the given qualified names as IDs and names.
3537
SymbolSlab generateSymbols(std::vector<std::string> QualifiedNames);

clang-tools-extra/clangd/unittests/XRefsTests.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,8 @@ TEST(FindImplementations, CaptureDefintion) {
17711771
void checkFindRefs(llvm::StringRef Test, bool UseIndex = false) {
17721772
Annotations T(Test);
17731773
auto TU = TestTU::withCode(T.code());
1774+
TU.ExtraArgs.push_back("-std=c++20");
1775+
17741776
auto AST = TU.build();
17751777
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
17761778
for (const auto &R : T.ranges())
@@ -1985,6 +1987,38 @@ TEST(FindReferences, WithinAST) {
19851987
checkFindRefs(Test);
19861988
}
19871989

1990+
TEST(FindReferences, ConceptsWithinAST) {
1991+
constexpr llvm::StringLiteral Code = R"cpp(
1992+
template <class T>
1993+
concept $def[[IsSmal^l]] = sizeof(T) <= 8;
1994+
1995+
template <class T>
1996+
concept IsSmallPtr = requires(T x) {
1997+
{ *x } -> [[IsSmal^l]];
1998+
};
1999+
2000+
[[IsSmall]] auto i = 'c';
2001+
template<[[IsSmal^l]] U> void foo();
2002+
template<class U> void bar() requires [[IsSmal^l]]<U>;
2003+
template<class U> requires [[IsSmal^l]]<U> void baz();
2004+
static_assert([[IsSma^ll]]<char>);
2005+
)cpp";
2006+
checkFindRefs(Code);
2007+
}
2008+
2009+
TEST(FindReferences, RequiresExprParameters) {
2010+
constexpr llvm::StringLiteral Code = R"cpp(
2011+
template <class T>
2012+
concept IsSmall = sizeof(T) <= 8;
2013+
2014+
template <class T>
2015+
concept IsSmallPtr = requires(T $def[[^x]]) {
2016+
{ *[[^x]] } -> IsSmall;
2017+
};
2018+
)cpp";
2019+
checkFindRefs(Code);
2020+
}
2021+
19882022
TEST(FindReferences, IncludeOverrides) {
19892023
llvm::StringRef Test =
19902024
R"cpp(

clang/include/clang/AST/RecursiveASTVisitor.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2795,9 +2795,10 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
27952795
if (!ExprReq->isExprSubstitutionFailure())
27962796
TRY_TO(TraverseStmt(ExprReq->getExpr()));
27972797
auto &RetReq = ExprReq->getReturnTypeRequirement();
2798-
if (RetReq.isTypeConstraint())
2799-
TRY_TO(TraverseTemplateParameterListHelper(
2800-
RetReq.getTypeConstraintTemplateParameterList()));
2798+
if (RetReq.isTypeConstraint()) {
2799+
TRY_TO(TraverseStmt(
2800+
RetReq.getTypeConstraint()->getImmediatelyDeclaredConstraint()));
2801+
}
28012802
} else {
28022803
auto *NestedReq = cast<concepts::NestedRequirement>(Req);
28032804
if (!NestedReq->isSubstitutionFailure())

clang/include/clang/Index/IndexSymbol.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ enum class SymbolKind : uint8_t {
5959
TemplateTypeParm,
6060
TemplateTemplateParm,
6161
NonTypeTemplateParm,
62+
63+
Concept, /// C++20 concept.
6264
};
6365

6466
enum class SymbolLanguage : uint8_t {

clang/lib/Index/IndexBody.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "IndexingContext.h"
10-
#include "clang/AST/RecursiveASTVisitor.h"
10+
#include "clang/AST/ASTConcept.h"
1111
#include "clang/AST/ASTLambda.h"
12+
#include "clang/AST/DeclCXX.h"
13+
#include "clang/AST/ExprConcepts.h"
14+
#include "clang/AST/RecursiveASTVisitor.h"
15+
#include "clang/AST/Type.h"
1216

1317
using namespace clang;
1418
using namespace clang::index;
@@ -455,10 +459,10 @@ class BodyIndexer : public RecursiveASTVisitor<BodyIndexer> {
455459
}
456460

457461
bool VisitParmVarDecl(ParmVarDecl* D) {
458-
// Index the parameters of lambda expression.
462+
// Index the parameters of lambda expression and requires expression.
459463
if (IndexCtx.shouldIndexFunctionLocalSymbols()) {
460464
const auto *DC = D->getDeclContext();
461-
if (DC && isLambdaCallOperator(DC))
465+
if (DC && (isLambdaCallOperator(DC) || isa<RequiresExprBodyDecl>(DC)))
462466
IndexCtx.handleDecl(D);
463467
}
464468
return true;
@@ -472,6 +476,21 @@ class BodyIndexer : public RecursiveASTVisitor<BodyIndexer> {
472476
Relations, E);
473477
return true;
474478
}
479+
480+
bool VisitConceptSpecializationExpr(ConceptSpecializationExpr *R) {
481+
IndexCtx.handleReference(R->getNamedConcept(), R->getConceptNameLoc(),
482+
Parent, ParentDC);
483+
return true;
484+
}
485+
486+
bool VisitTemplateTypeParmDecl(TemplateTypeParmDecl *D) {
487+
// This handles references in return type requirements of RequiresExpr.
488+
// E.g. `requires (T x) { {*x} -> ConceptRef }`
489+
if (auto *C = D->getTypeConstraint())
490+
IndexCtx.handleReference(C->getNamedConcept(), C->getConceptNameLoc(),
491+
Parent, ParentDC);
492+
return true;
493+
}
475494
};
476495

477496
} // anonymous namespace

clang/lib/Index/IndexDecl.cpp

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "IndexingContext.h"
10+
#include "clang/AST/ASTConcept.h"
1011
#include "clang/AST/Attr.h"
1112
#include "clang/AST/Decl.h"
13+
#include "clang/AST/DeclTemplate.h"
1214
#include "clang/AST/DeclVisitor.h"
1315
#include "clang/Index/IndexDataConsumer.h"
16+
#include "clang/Index/IndexSymbol.h"
1417

1518
using namespace clang;
1619
using namespace index;
@@ -129,6 +132,8 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
129132
}
130133
}
131134
}
135+
if (auto *C = D->getTrailingRequiresClause())
136+
IndexCtx.indexBody(C, Parent);
132137
}
133138

134139
bool handleObjCMethod(const ObjCMethodDecl *D,
@@ -688,36 +693,52 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
688693
return true;
689694
}
690695

691-
bool VisitTemplateDecl(const TemplateDecl *D) {
696+
void indexTemplateParameters(TemplateParameterList *Params,
697+
const NamedDecl *Parent) {
698+
for (const NamedDecl *TP : *Params) {
699+
if (IndexCtx.shouldIndexTemplateParameters())
700+
IndexCtx.handleDecl(TP);
701+
if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(TP)) {
702+
if (TTP->hasDefaultArgument())
703+
IndexCtx.indexTypeSourceInfo(TTP->getDefaultArgumentInfo(), Parent);
704+
if (auto *C = TTP->getTypeConstraint())
705+
IndexCtx.handleReference(C->getNamedConcept(), C->getConceptNameLoc(),
706+
Parent, TTP->getLexicalDeclContext());
707+
} else if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TP)) {
708+
if (NTTP->hasDefaultArgument())
709+
IndexCtx.indexBody(NTTP->getDefaultArgument(), Parent);
710+
} else if (const auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(TP)) {
711+
if (TTPD->hasDefaultArgument())
712+
handleTemplateArgumentLoc(TTPD->getDefaultArgument(), Parent,
713+
TP->getLexicalDeclContext());
714+
}
715+
}
716+
if (auto *R = Params->getRequiresClause())
717+
IndexCtx.indexBody(R, Parent);
718+
}
692719

720+
bool VisitTemplateDecl(const TemplateDecl *D) {
693721
const NamedDecl *Parent = D->getTemplatedDecl();
694722
if (!Parent)
695723
return true;
696724

697725
// Index the default values for the template parameters.
698-
if (D->getTemplateParameters() &&
699-
shouldIndexTemplateParameterDefaultValue(Parent)) {
700-
const TemplateParameterList *Params = D->getTemplateParameters();
701-
for (const NamedDecl *TP : *Params) {
702-
if (IndexCtx.shouldIndexTemplateParameters())
703-
IndexCtx.handleDecl(TP);
704-
if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(TP)) {
705-
if (TTP->hasDefaultArgument())
706-
IndexCtx.indexTypeSourceInfo(TTP->getDefaultArgumentInfo(), Parent);
707-
} else if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TP)) {
708-
if (NTTP->hasDefaultArgument())
709-
IndexCtx.indexBody(NTTP->getDefaultArgument(), Parent);
710-
} else if (const auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(TP)) {
711-
if (TTPD->hasDefaultArgument())
712-
handleTemplateArgumentLoc(TTPD->getDefaultArgument(), Parent,
713-
TP->getLexicalDeclContext());
714-
}
715-
}
726+
auto *Params = D->getTemplateParameters();
727+
if (Params && shouldIndexTemplateParameterDefaultValue(Parent)) {
728+
indexTemplateParameters(Params, Parent);
716729
}
717730

718731
return Visit(Parent);
719732
}
720733

734+
bool VisitConceptDecl(const ConceptDecl *D) {
735+
if (auto *Params = D->getTemplateParameters())
736+
indexTemplateParameters(Params, D);
737+
if (auto *E = D->getConstraintExpr())
738+
IndexCtx.indexBody(E, D);
739+
return IndexCtx.handleDecl(D);
740+
}
741+
721742
bool VisitFriendDecl(const FriendDecl *D) {
722743
if (auto ND = D->getFriendDecl()) {
723744
// FIXME: Ignore a class template in a dependent context, these are not

clang/lib/Index/IndexSymbol.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ SymbolInfo index::getSymbolInfo(const Decl *D) {
371371
case Decl::NonTypeTemplateParm:
372372
Info.Kind = SymbolKind::NonTypeTemplateParm;
373373
break;
374+
case Decl::Concept:
375+
Info.Kind = SymbolKind::Concept;
376+
break;
374377
// Other decls get the 'unknown' kind.
375378
default:
376379
break;
@@ -535,6 +538,8 @@ StringRef index::getSymbolKindString(SymbolKind K) {
535538
case SymbolKind::TemplateTypeParm: return "template-type-param";
536539
case SymbolKind::TemplateTemplateParm: return "template-template-param";
537540
case SymbolKind::NonTypeTemplateParm: return "non-type-template-param";
541+
case SymbolKind::Concept:
542+
return "concept";
538543
}
539544
llvm_unreachable("invalid symbol kind");
540545
}

clang/lib/Index/IndexTypeSourceInfo.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "IndexingContext.h"
10+
#include "clang/AST/ASTConcept.h"
11+
#include "clang/AST/PrettyPrinter.h"
1012
#include "clang/AST/RecursiveASTVisitor.h"
13+
#include "clang/AST/TypeLoc.h"
1114
#include "llvm/ADT/ScopeExit.h"
1215

1316
using namespace clang;
@@ -77,6 +80,13 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> {
7780
return true;
7881
}
7982

83+
bool VisitAutoTypeLoc(AutoTypeLoc TL) {
84+
if (auto *C = TL.getNamedConcept())
85+
return IndexCtx.handleReference(C, TL.getConceptNameLoc(), Parent,
86+
ParentDC);
87+
return true;
88+
}
89+
8090
bool traverseParamVarHelper(ParmVarDecl *D) {
8191
TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc()));
8292
if (D->getTypeSourceInfo())

0 commit comments

Comments
 (0)