Skip to content

Commit 75e16fd

Browse files
committed
[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 a037eac commit 75e16fd

18 files changed

+185
-33
lines changed

Diff for: clang-tools-extra/clangd/CodeComplete.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
129129
case SK::TemplateTypeParm:
130130
case SK::TemplateTemplateParm:
131131
return CompletionItemKind::TypeParameter;
132+
case SK::Concept:
133+
return CompletionItemKind::Interface;
132134
}
133135
llvm_unreachable("Unhandled clang::index::SymbolKind.");
134136
}

Diff for: clang-tools-extra/clangd/Protocol.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
301301
case index::SymbolKind::TemplateTemplateParm:
302302
case index::SymbolKind::TemplateTypeParm:
303303
return SymbolKind::TypeParameter;
304+
case index::SymbolKind::Concept:
305+
return SymbolKind::Interface;
304306
}
305307
llvm_unreachable("invalid symbol kind");
306308
}

Diff for: clang-tools-extra/clangd/Quality.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ categorize(const index::SymbolInfo &D) {
122122
case index::SymbolKind::TypeAlias:
123123
case index::SymbolKind::TemplateTypeParm:
124124
case index::SymbolKind::TemplateTemplateParm:
125+
case index::SymbolKind::Concept:
125126
return SymbolQualitySignals::Type;
126127
case index::SymbolKind::Function:
127128
case index::SymbolKind::ClassMethod:

Diff for: clang-tools-extra/clangd/index/SymbolCollector.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ bool shouldCollectIncludePath(index::SymbolKind Kind) {
8888
case SK::Function:
8989
case SK::Variable:
9090
case SK::EnumConstant:
91+
case SK::Concept:
9192
return true;
9293
default:
9394
return false;

Diff for: clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -3565,6 +3565,38 @@ TEST(CompletionTest, CommentParamName) {
35653565
IsEmpty());
35663566
}
35673567

3568+
TEST(CompletionTest, Concepts) {
3569+
Annotations Code(R"cpp(
3570+
template<class T>
3571+
concept A = sizeof(T) <= 8;
3572+
3573+
template<$tparam^A U>
3574+
int foo();
3575+
3576+
template<class T>
3577+
concept b = $other^A<T> && $other^sizeof(T) % 2 == 0 || $other^A<T> && sizeof(T) == 1;
3578+
3579+
$other^A<T> auto i = 19;
3580+
)cpp");
3581+
TestTU TU;
3582+
TU.Code = Code.code().str();
3583+
TU.ExtraArgs = {"-std=c++20"};
3584+
3585+
std::vector<Symbol> Syms = {conceptSym("same_as")};
3586+
for (auto P : Code.points("tparam")) {
3587+
ASSERT_THAT(completions(TU, P, Syms).Completions,
3588+
AllOf(Contains(named("A")), Contains(named("same_as")),
3589+
Contains(named("class")), Contains(named("typename"))))
3590+
<< "Completing template parameter at position " << P;
3591+
}
3592+
3593+
for (auto P : Code.points("other")) {
3594+
EXPECT_THAT(completions(TU, P, Syms).Completions,
3595+
AllOf(Contains(named("A")), Contains(named("same_as"))))
3596+
<< "Completing 'requires' expression at position " << P;
3597+
}
3598+
}
3599+
35683600
TEST(SignatureHelp, DocFormat) {
35693601
Annotations Code(R"cpp(
35703602
// Comment `with` markup.

Diff for: clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp

+12
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
}
@@ -1962,6 +1963,17 @@ TEST_F(SymbolCollectorTest, Reserved) {
19621963
EXPECT_THAT(Symbols, IsEmpty());
19631964
}
19641965

1966+
TEST_F(SymbolCollectorTest, Concepts) {
1967+
const char *Header = R"cpp(
1968+
template <class T>
1969+
concept A = sizeof(T) <= 8;
1970+
)cpp";
1971+
runSymbolCollector("", Header, {"-std=c++20"});
1972+
EXPECT_THAT(Symbols,
1973+
UnorderedElementsAre(AllOf(
1974+
qName("A"), hasKind(clang::index::SymbolKind::Concept))));
1975+
}
1976+
19651977
} // namespace
19661978
} // namespace clangd
19671979
} // namespace clang

Diff for: clang-tools-extra/clangd/unittests/TestIndex.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ Symbol ns(llvm::StringRef Name) {
7777
return sym(Name, index::SymbolKind::Namespace, "@N@\\0");
7878
}
7979

80+
Symbol conceptSym(llvm::StringRef Name) {
81+
return sym(Name, index::SymbolKind::Concept, "@CT@\\0");
82+
}
83+
8084
SymbolSlab generateSymbols(std::vector<std::string> QualifiedNames) {
8185
SymbolSlab::Builder Slab;
8286
for (llvm::StringRef QName : QualifiedNames)

Diff for: clang-tools-extra/clangd/unittests/TestIndex.h

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Symbol enm(llvm::StringRef Name);
3131
Symbol var(llvm::StringRef Name);
3232
// Creates a namespace symbol.
3333
Symbol ns(llvm::StringRef Name);
34+
// Create a C++20 concept symbol.
35+
Symbol conceptSym(llvm::StringRef Name);
3436

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

Diff for: clang-tools-extra/clangd/unittests/XRefsTests.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,8 @@ TEST(FindType, All) {
18501850
void checkFindRefs(llvm::StringRef Test, bool UseIndex = false) {
18511851
Annotations T(Test);
18521852
auto TU = TestTU::withCode(T.code());
1853+
TU.ExtraArgs.push_back("-std=c++20");
1854+
18531855
auto AST = TU.build();
18541856
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
18551857
for (const auto &R : T.ranges())
@@ -2064,6 +2066,38 @@ TEST(FindReferences, WithinAST) {
20642066
checkFindRefs(Test);
20652067
}
20662068

2069+
TEST(FindReferences, ConceptsWithinAST) {
2070+
constexpr llvm::StringLiteral Code = R"cpp(
2071+
template <class T>
2072+
concept $def[[IsSmal^l]] = sizeof(T) <= 8;
2073+
2074+
template <class T>
2075+
concept IsSmallPtr = requires(T x) {
2076+
{ *x } -> [[IsSmal^l]];
2077+
};
2078+
2079+
[[IsSmall]] auto i = 'c';
2080+
template<[[IsSmal^l]] U> void foo();
2081+
template<class U> void bar() requires [[IsSmal^l]]<U>;
2082+
template<class U> requires [[IsSmal^l]]<U> void baz();
2083+
static_assert([[IsSma^ll]]<char>);
2084+
)cpp";
2085+
checkFindRefs(Code);
2086+
}
2087+
2088+
TEST(FindReferences, RequiresExprParameters) {
2089+
constexpr llvm::StringLiteral Code = R"cpp(
2090+
template <class T>
2091+
concept IsSmall = sizeof(T) <= 8;
2092+
2093+
template <class T>
2094+
concept IsSmallPtr = requires(T $def[[^x]]) {
2095+
{ *[[^x]] } -> IsSmall;
2096+
};
2097+
)cpp";
2098+
checkFindRefs(Code);
2099+
}
2100+
20672101
TEST(FindReferences, IncludeOverrides) {
20682102
llvm::StringRef Test =
20692103
R"cpp(

Diff for: clang/include/clang/AST/RecursiveASTVisitor.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -2841,9 +2841,10 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
28412841
if (!ExprReq->isExprSubstitutionFailure())
28422842
TRY_TO(TraverseStmt(ExprReq->getExpr()));
28432843
auto &RetReq = ExprReq->getReturnTypeRequirement();
2844-
if (RetReq.isTypeConstraint())
2845-
TRY_TO(TraverseTemplateParameterListHelper(
2846-
RetReq.getTypeConstraintTemplateParameterList()));
2844+
if (RetReq.isTypeConstraint()) {
2845+
TRY_TO(TraverseStmt(
2846+
RetReq.getTypeConstraint()->getImmediatelyDeclaredConstraint()));
2847+
}
28472848
} else {
28482849
auto *NestedReq = cast<concepts::NestedRequirement>(Req);
28492850
if (!NestedReq->isSubstitutionFailure())

Diff for: clang/include/clang/Index/IndexSymbol.h

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ enum class SymbolKind : uint8_t {
5757
TemplateTypeParm,
5858
TemplateTemplateParm,
5959
NonTypeTemplateParm,
60+
61+
Concept, /// C++20 concept.
6062
};
6163

6264
enum class SymbolLanguage : uint8_t {

Diff for: clang/lib/Index/IndexBody.cpp

+22-3
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

Diff for: clang/lib/Index/IndexDecl.cpp

+40-19
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

Diff for: clang/lib/Index/IndexSymbol.cpp

+5
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;
@@ -534,6 +537,8 @@ StringRef index::getSymbolKindString(SymbolKind K) {
534537
case SymbolKind::TemplateTypeParm: return "template-type-param";
535538
case SymbolKind::TemplateTemplateParm: return "template-template-param";
536539
case SymbolKind::NonTypeTemplateParm: return "non-type-template-param";
540+
case SymbolKind::Concept:
541+
return "concept";
537542
}
538543
llvm_unreachable("invalid symbol kind");
539544
}

Diff for: clang/lib/Index/IndexTypeSourceInfo.cpp

+10
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)