[clang] [clang-tools-extra] Handle recording inheritance for templates (PR #177273)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 10 11:53:01 PDT 2026
https://github.com/timon-ul updated https://github.com/llvm/llvm-project/pull/177273
>From ff644a0ff41d1521436567b10501e112bda93759 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Sat, 17 Jan 2026 03:16:06 +0100
Subject: [PATCH 01/14] First step towards indexing template instantiations
---
clang-tools-extra/clangd/XRefs.cpp | 42 +++++++++++--------
.../clangd/index/SymbolCollector.cpp | 13 +++++-
.../clangd/unittests/XRefsTests.cpp | 20 +++++++++
clang/include/clang/Index/IndexingOptions.h | 2 +-
clang/lib/Index/IndexDecl.cpp | 33 ++++++++++++++-
clang/lib/Index/IndexTypeSourceInfo.cpp | 9 ++++
clang/lib/Index/IndexingContext.cpp | 4 +-
7 files changed, 98 insertions(+), 25 deletions(-)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 8a24d19a7d129..a517abba7762a 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -306,25 +306,31 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
RelationsRequest Req;
Req.Predicate = Predicate;
- Req.Subjects = std::move(IDs);
+ llvm::DenseSet<SymbolID> RecursiveSearch = std::move(IDs);
std::vector<LocatedSymbol> Results;
- Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
- auto DeclLoc =
- indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath);
- if (!DeclLoc) {
- elog("Find overrides: {0}", DeclLoc.takeError());
- return;
- }
- Results.emplace_back();
- Results.back().Name = Object.Name.str();
- Results.back().PreferredDeclaration = *DeclLoc;
- auto DefLoc = indexToLSPLocation(Object.Definition, MainFilePath);
- if (!DefLoc) {
- elog("Failed to convert location: {0}", DefLoc.takeError());
- return;
- }
- Results.back().Definition = *DefLoc;
- });
+
+ while (!RecursiveSearch.empty()) {
+ Req.Subjects = std::move(RecursiveSearch);
+ RecursiveSearch = {};
+ Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
+ auto DeclLoc =
+ indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath);
+ if (!DeclLoc) {
+ elog("Find overrides: {0}", DeclLoc.takeError());
+ return;
+ }
+ Results.emplace_back();
+ Results.back().Name = Object.Name.str();
+ Results.back().PreferredDeclaration = *DeclLoc;
+ auto DefLoc = indexToLSPLocation(Object.Definition, MainFilePath);
+ if (!DefLoc) {
+ elog("Failed to convert location: {0}", DefLoc.takeError());
+ return;
+ }
+ Results.back().Definition = *DefLoc;
+ RecursiveSearch.insert(Object.ID);
+ });
+ }
return Results;
}
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index bd974e4c18818..8d905ad1fea10 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -603,6 +603,12 @@ bool SymbolCollector::handleDeclOccurrence(
assert(ASTCtx && PP && HeaderFileURIs);
assert(CompletionAllocator && CompletionTUInfo);
assert(ASTNode.OrigD);
+ const NamedDecl *NOD = dyn_cast<NamedDecl>(ASTNode.OrigD);
+ std::string NameOD = "";
+ if (NOD){
+ NameOD = printName(*ASTCtx, *NOD);
+ NameOD += printTemplateSpecializationArgs(*NOD);
+ }
// Indexing API puts canonical decl into D, which might not have a valid
// source location for implicit/built-in decls. Fallback to original decl in
// such cases.
@@ -896,9 +902,12 @@ void SymbolCollector::processRelations(
// in the index and find nothing, but that's a situation they
// probably need to handle for other reasons anyways.
// We currently do (B) because it's simpler.
- if (*RKind == RelationKind::BaseOf)
+ if (*RKind == RelationKind::BaseOf) {
+ std::string SubjectName = printName(*ASTCtx, ND) + printTemplateSpecializationArgs(ND);
+ const auto *Sym = Symbols.find(ObjectID);
+ std::string ObjectName = (Sym->Scope + Sym->Name + Sym->TemplateSpecializationArgs).str();
this->Relations.insert({ID, *RKind, ObjectID});
- else if (*RKind == RelationKind::OverriddenBy)
+ } else if (*RKind == RelationKind::OverriddenBy)
this->Relations.insert({ObjectID, *RKind, ID});
}
}
diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 4106c6cf7b2d0..5cefbeb78071a 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1958,6 +1958,26 @@ TEST(FindImplementations, InheritanceObjC) {
Code.range("protocolDef"))));
}
+TEST(FindImplementations, InheritanceRecursive) {
+ Annotations Main(R"cpp(
+ template <typename... T>
+ struct Inherit : T... {};
+
+ struct Al$Alpha^pha {};
+
+ struct $Impl1[[impl]]: Inherit<Alpha> {};
+ )cpp");
+
+ TestTU TU;
+ TU.Code = std::string(Main.code());
+ auto AST = TU.build();
+ auto Index = TU.index();
+
+ EXPECT_THAT(
+ findImplementations(AST, Main.point("Alpha"), Index.get()),
+ ElementsAre(sym("impl", Main.range("Impl1"), Main.range("Impl1"))));
+}
+
TEST(FindImplementations, CaptureDefinition) {
llvm::StringRef Test = R"cpp(
struct Base {
diff --git a/clang/include/clang/Index/IndexingOptions.h b/clang/include/clang/Index/IndexingOptions.h
index c670797e9fa60..1094ec46dc9af 100644
--- a/clang/include/clang/Index/IndexingOptions.h
+++ b/clang/include/clang/Index/IndexingOptions.h
@@ -27,7 +27,7 @@ struct IndexingOptions {
SystemSymbolFilterKind SystemSymbolFilter =
SystemSymbolFilterKind::DeclarationsOnly;
bool IndexFunctionLocals = false;
- bool IndexImplicitInstantiation = false;
+ bool IndexImplicitInstantiation = true;
bool IndexMacros = true;
// Whether to index macro definitions in the Preprocessor when preprocessor
// callback is not available (e.g. after parsing has finished). Note that
diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp
index df875e0b40079..51b400f915bd3 100644
--- a/clang/lib/Index/IndexDecl.cpp
+++ b/clang/lib/Index/IndexDecl.cpp
@@ -14,6 +14,7 @@
#include "clang/AST/DeclVisitor.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexSymbol.h"
+#include "llvm/Support/Casting.h"
using namespace clang;
using namespace index;
@@ -734,7 +735,37 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
indexTemplateParameters(Params, Parent);
}
- return Visit(Parent);
+ bool shouldContinue = Visit(Parent);
+ if (!shouldContinue)
+ return false;
+
+ // TODO: cleanup maybe, so far copy paste from
+ // RecursiveASTVisitor::TraverseTemplateInstantiation, we have technically
+ // `shouldIndexImplicitInstantiation()` available here, but the logic is
+ // different and I am confused.
+ if (const auto *CTD = llvm::dyn_cast<ClassTemplateDecl>(D))
+ for (auto *SD : CTD->specializations())
+ for (auto *RD : SD->redecls()) {
+ assert(!cast<CXXRecordDecl>(RD)->isInjectedClassName());
+ switch (cast<ClassTemplateSpecializationDecl>(RD)
+ ->getSpecializationKind()) {
+ // Visit the implicit instantiations with the requested pattern.
+ case TSK_Undeclared:
+ case TSK_ImplicitInstantiation:
+ Visit(RD);
+ break;
+
+ // We don't need to do anything on an explicit instantiation
+ // or explicit specialization because there will be an explicit
+ // node for it elsewhere.
+ case TSK_ExplicitInstantiationDeclaration:
+ case TSK_ExplicitInstantiationDefinition:
+ case TSK_ExplicitSpecialization:
+ break;
+ }
+ }
+
+ return true;
}
bool VisitConceptDecl(const ConceptDecl *D) {
diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp
index c9ad36b5406c5..be3d75e84c351 100644
--- a/clang/lib/Index/IndexTypeSourceInfo.cpp
+++ b/clang/lib/Index/IndexTypeSourceInfo.cpp
@@ -10,6 +10,7 @@
#include "clang/AST/ASTConcept.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/TypeBase.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Sema/HeuristicResolver.h"
#include "llvm/ADT/ScopeExit.h"
@@ -193,6 +194,14 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> {
return true;
}
+ // bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL,
+ // bool TraverseQualifier) {
+ // auto Type = TL.getAs<TemplateSpecializationTypeLoc>();
+ // if (!Type.isNull())
+ // TraverseTemplateSpecializationTypeLoc(Type, TraverseQualifier);
+ // return true;
+ // }
+
bool VisitDeducedTemplateSpecializationTypeLoc(DeducedTemplateSpecializationTypeLoc TL) {
auto *T = TL.getTypePtr();
if (!T)
diff --git a/clang/lib/Index/IndexingContext.cpp b/clang/lib/Index/IndexingContext.cpp
index bdd6c5acf1d34..c6843cf46cf8e 100644
--- a/clang/lib/Index/IndexingContext.cpp
+++ b/clang/lib/Index/IndexingContext.cpp
@@ -402,9 +402,7 @@ bool IndexingContext::handleDeclOccurrence(const Decl *D, SourceLocation Loc,
if (!OrigD)
OrigD = D;
- if (isTemplateImplicitInstantiation(D)) {
- if (!IsRef)
- return true;
+ if (isTemplateImplicitInstantiation(D) && IsRef) {
D = adjustTemplateImplicitInstantiation(D);
if (!D)
return true;
>From 511ba41d9153e13247fd5fb9eb61ec330dfb4e21 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Sat, 17 Jan 2026 03:20:22 +0100
Subject: [PATCH 02/14] New methods for visiting SubstTemplateType
---
clang/lib/Index/IndexTypeSourceInfo.cpp | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp
index be3d75e84c351..e16d8ac791898 100644
--- a/clang/lib/Index/IndexTypeSourceInfo.cpp
+++ b/clang/lib/Index/IndexTypeSourceInfo.cpp
@@ -194,13 +194,21 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> {
return true;
}
- // bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL,
- // bool TraverseQualifier) {
- // auto Type = TL.getAs<TemplateSpecializationTypeLoc>();
- // if (!Type.isNull())
- // TraverseTemplateSpecializationTypeLoc(Type, TraverseQualifier);
- // return true;
- // }
+ bool VisitSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL) {
+ auto QT = TL.getTypePtr()->getReplacementType();
+ auto *T = QT->getAsNonAliasTemplateSpecializationType();
+ if (!T)
+ return true;
+ HandleTemplateSpecializationTypeLoc(
+ T->getTemplateName(), TL.getTemplateNameLoc(), T->getAsCXXRecordDecl(),
+ T->isTypeAlias());
+ return true;
+ }
+
+ bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL,
+ bool TraverseQualifier) {
+ return true;
+ }
bool VisitDeducedTemplateSpecializationTypeLoc(DeducedTemplateSpecializationTypeLoc TL) {
auto *T = TL.getTypePtr();
>From b5ffe8fb17140df17999a61693064bec25248ec2 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Wed, 21 Jan 2026 23:20:57 +0100
Subject: [PATCH 03/14] Implemented template inheritance handling
---
.../clangd/index/SymbolCollector.cpp | 22 ++++++------
.../clangd/index/SymbolCollector.h | 2 +-
.../clangd/unittests/XRefsTests.cpp | 36 ++++++++++++++-----
clang/include/clang/Index/IndexingOptions.h | 2 +-
clang/lib/Index/IndexDecl.cpp | 33 +++++++----------
clang/lib/Index/IndexTypeSourceInfo.cpp | 19 +++++-----
6 files changed, 61 insertions(+), 53 deletions(-)
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 8d905ad1fea10..f8ff9bf277ceb 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -603,12 +603,7 @@ bool SymbolCollector::handleDeclOccurrence(
assert(ASTCtx && PP && HeaderFileURIs);
assert(CompletionAllocator && CompletionTUInfo);
assert(ASTNode.OrigD);
- const NamedDecl *NOD = dyn_cast<NamedDecl>(ASTNode.OrigD);
- std::string NameOD = "";
- if (NOD){
- NameOD = printName(*ASTCtx, *NOD);
- NameOD += printTemplateSpecializationArgs(*NOD);
- }
+
// Indexing API puts canonical decl into D, which might not have a valid
// source location for implicit/built-in decls. Fallback to original decl in
// such cases.
@@ -679,7 +674,7 @@ bool SymbolCollector::handleDeclOccurrence(
// refs, because the indexing code only populates relations for specific
// occurrences. For example, RelationBaseOf is only populated for the
// occurrence inside the base-specifier.
- processRelations(*ND, ID, Relations);
+ processRelations(ID, *ASTNode.OrigD, Relations);
bool CollectRef = static_cast<bool>(Opts.RefFilter & toRefKind(Roles));
// Unlike other fields, e.g. Symbols (which use spelling locations), we use
@@ -881,7 +876,7 @@ bool SymbolCollector::handleMacroOccurrence(const IdentifierInfo *Name,
}
void SymbolCollector::processRelations(
- const NamedDecl &ND, const SymbolID &ID,
+ const SymbolID &ID, const Decl &OrigD,
ArrayRef<index::SymbolRelation> Relations) {
for (const auto &R : Relations) {
auto RKind = indexableRelation(R);
@@ -903,10 +898,15 @@ void SymbolCollector::processRelations(
// probably need to handle for other reasons anyways.
// We currently do (B) because it's simpler.
if (*RKind == RelationKind::BaseOf) {
- std::string SubjectName = printName(*ASTCtx, ND) + printTemplateSpecializationArgs(ND);
- const auto *Sym = Symbols.find(ObjectID);
- std::string ObjectName = (Sym->Scope + Sym->Name + Sym->TemplateSpecializationArgs).str();
this->Relations.insert({ID, *RKind, ObjectID});
+ // If the Subject is a template, we also want a relation to the
+ // template instantiation (OrigD) to record inheritance chains.
+ if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(&OrigD);
+ CTSD && !CTSD->isExplicitSpecialization()) {
+ auto OrigID = getSymbolIDCached(&OrigD);
+ if (OrigID)
+ this->Relations.insert({OrigID, *RKind, ObjectID});
+ }
} else if (*RKind == RelationKind::OverriddenBy)
this->Relations.insert({ObjectID, *RKind, ID});
}
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h
index 4d51d747639b1..54a12ba122240 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.h
+++ b/clang-tools-extra/clangd/index/SymbolCollector.h
@@ -169,7 +169,7 @@ class SymbolCollector : public index::IndexDataConsumer {
bool IsMainFileSymbol);
void addDefinition(const NamedDecl &, const Symbol &DeclSymbol,
bool SkipDocCheck);
- void processRelations(const NamedDecl &ND, const SymbolID &ID,
+ void processRelations(const SymbolID &ID, const Decl &OrigD,
ArrayRef<index::SymbolRelation> Relations);
std::optional<SymbolLocation> getTokenLocation(SourceLocation TokLoc);
diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 5cefbeb78071a..9a382f4a8c257 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1878,8 +1878,8 @@ TEST(FindImplementations, Inheritance) {
virtual void B$2^ar();
void Concrete(); // No implementations for concrete methods.
};
- struct Child2 : Child1 {
- void $3[[Foo]]() override;
+ struct $0[[Child2]] : Child1 {
+ void $1[[$3[[Foo]]]]() override;
void $2[[Bar]]() override;
};
void FromReference() {
@@ -1958,14 +1958,27 @@ TEST(FindImplementations, InheritanceObjC) {
Code.range("protocolDef"))));
}
-TEST(FindImplementations, InheritanceRecursive) {
+TEST(FindImplementations, InheritanceTemplate) {
Annotations Main(R"cpp(
+ class Fi$First^rst {};
+
+ class Sec$Second^ond {};
+
+ class Th$Third^ird {};
+
template <typename... T>
- struct Inherit : T... {};
+ struct $Third[[Inherit]] : T... {};
+
+ template struct $First[[Inherit]]<First>;
+
+ template<>
+ struct $Second[[Inherit]]<Second> : Second {};
- struct Al$Alpha^pha {};
+ class $First[[Battler]] : Inherit<First> {};
- struct $Impl1[[impl]]: Inherit<Alpha> {};
+ class $Second[[Beatrice]] : Inherit<Second> {};
+
+ class $Third[[Maria]] : Inherit<Third> {};
)cpp");
TestTU TU;
@@ -1973,9 +1986,14 @@ TEST(FindImplementations, InheritanceRecursive) {
auto AST = TU.build();
auto Index = TU.index();
- EXPECT_THAT(
- findImplementations(AST, Main.point("Alpha"), Index.get()),
- ElementsAre(sym("impl", Main.range("Impl1"), Main.range("Impl1"))));
+ EXPECT_THAT(findImplementations(AST, Main.point("First"), Index.get()),
+ UnorderedPointwise(declRange(), Main.ranges("First")));
+
+ EXPECT_THAT(findImplementations(AST, Main.point("Second"), Index.get()),
+ UnorderedPointwise(declRange(), Main.ranges("Second")));
+
+ EXPECT_THAT(findImplementations(AST, Main.point("Third"), Index.get()),
+ UnorderedPointwise(declRange(), Main.ranges("Third")));
}
TEST(FindImplementations, CaptureDefinition) {
diff --git a/clang/include/clang/Index/IndexingOptions.h b/clang/include/clang/Index/IndexingOptions.h
index 1094ec46dc9af..c670797e9fa60 100644
--- a/clang/include/clang/Index/IndexingOptions.h
+++ b/clang/include/clang/Index/IndexingOptions.h
@@ -27,7 +27,7 @@ struct IndexingOptions {
SystemSymbolFilterKind SystemSymbolFilter =
SystemSymbolFilterKind::DeclarationsOnly;
bool IndexFunctionLocals = false;
- bool IndexImplicitInstantiation = true;
+ bool IndexImplicitInstantiation = false;
bool IndexMacros = true;
// Whether to index macro definitions in the Preprocessor when preprocessor
// callback is not available (e.g. after parsing has finished). Note that
diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp
index 51b400f915bd3..f0fe1d0af7791 100644
--- a/clang/lib/Index/IndexDecl.cpp
+++ b/clang/lib/Index/IndexDecl.cpp
@@ -739,30 +739,21 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
if (!shouldContinue)
return false;
- // TODO: cleanup maybe, so far copy paste from
- // RecursiveASTVisitor::TraverseTemplateInstantiation, we have technically
- // `shouldIndexImplicitInstantiation()` available here, but the logic is
- // different and I am confused.
+ // Only check instantiation if D is canonical to prevent infinite cycling
+ if (D != D->getCanonicalDecl())
+ return true;
+
if (const auto *CTD = llvm::dyn_cast<ClassTemplateDecl>(D))
for (auto *SD : CTD->specializations())
for (auto *RD : SD->redecls()) {
- assert(!cast<CXXRecordDecl>(RD)->isInjectedClassName());
- switch (cast<ClassTemplateSpecializationDecl>(RD)
- ->getSpecializationKind()) {
- // Visit the implicit instantiations with the requested pattern.
- case TSK_Undeclared:
- case TSK_ImplicitInstantiation:
- Visit(RD);
- break;
-
- // We don't need to do anything on an explicit instantiation
- // or explicit specialization because there will be an explicit
- // node for it elsewhere.
- case TSK_ExplicitInstantiationDeclaration:
- case TSK_ExplicitInstantiationDefinition:
- case TSK_ExplicitSpecialization:
- break;
- }
+ auto *CTSD = cast<ClassTemplateSpecializationDecl>(RD);
+ // For now we are only interested in instantiations with inheritance.
+ if (!CTSD->hasDefinition() || CTSD->bases().empty())
+ continue;
+ // Explicit specialization is handled elsewhere
+ if (CTSD->isExplicitSpecialization())
+ continue;
+ Visit(RD);
}
return true;
diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp
index e16d8ac791898..bb62b7d6f8d87 100644
--- a/clang/lib/Index/IndexTypeSourceInfo.cpp
+++ b/clang/lib/Index/IndexTypeSourceInfo.cpp
@@ -194,19 +194,18 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> {
return true;
}
- bool VisitSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL) {
- auto QT = TL.getTypePtr()->getReplacementType();
- auto *T = QT->getAsNonAliasTemplateSpecializationType();
+ bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL,
+ bool TraverseQualifier) {
+ const auto *T = TL.getTypePtr();
if (!T)
return true;
- HandleTemplateSpecializationTypeLoc(
- T->getTemplateName(), TL.getTemplateNameLoc(), T->getAsCXXRecordDecl(),
- T->isTypeAlias());
- return true;
- }
+ auto QT = T->getReplacementType();
+ if (QT.isNull())
+ return true;
+
+ IndexCtx.handleReference(QT->getAsCXXRecordDecl(), TL.getNameLoc(), Parent,
+ ParentDC, SymbolRoleSet(), Relations);
- bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL,
- bool TraverseQualifier) {
return true;
}
>From 53c04f63a1a5583d4200a86e7d065e5ec11b4cd2 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Thu, 22 Jan 2026 00:05:17 +0100
Subject: [PATCH 04/14] Errors do not meant we shouldn't keep recursing
---
clang-tools-extra/clangd/XRefs.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index a517abba7762a..79decd35baadd 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -313,6 +313,7 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
Req.Subjects = std::move(RecursiveSearch);
RecursiveSearch = {};
Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
+ RecursiveSearch.insert(Object.ID);
auto DeclLoc =
indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath);
if (!DeclLoc) {
@@ -328,7 +329,6 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
return;
}
Results.back().Definition = *DefLoc;
- RecursiveSearch.insert(Object.ID);
});
}
return Results;
>From 6502814383b0bc95a0f69c05bf0aa126e4a9fa51 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Thu, 22 Jan 2026 00:28:59 +0100
Subject: [PATCH 05/14] Preventing refs for implicit instantiations
---
clang-tools-extra/clangd/index/SymbolCollector.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index f8ff9bf277ceb..30f34324f5e58 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -677,6 +677,13 @@ bool SymbolCollector::handleDeclOccurrence(
processRelations(ID, *ASTNode.OrigD, Relations);
bool CollectRef = static_cast<bool>(Opts.RefFilter & toRefKind(Roles));
+ // For now we only want the bare minimum of information for a class
+ // instantiation such that we have symbols for the `BaseOf` relation.
+ if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D);
+ CTSD && CTSD->hasDefinition() && !CTSD->bases().empty() &&
+ !CTSD->isExplicitSpecialization()) {
+ CollectRef = false;
+ }
// Unlike other fields, e.g. Symbols (which use spelling locations), we use
// file locations for references (as it aligns the behavior of clangd's
// AST-based xref).
>From 195fd77fca387bff1a4594437b0fb433ef243331 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Thu, 22 Jan 2026 11:05:50 +0100
Subject: [PATCH 06/14] More careful handling of casts
---
clang/lib/Index/IndexDecl.cpp | 5 ++---
clang/lib/Index/IndexTypeSourceInfo.cpp | 9 +++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp
index f0fe1d0af7791..35f8f7e43ed7d 100644
--- a/clang/lib/Index/IndexDecl.cpp
+++ b/clang/lib/Index/IndexDecl.cpp
@@ -14,7 +14,6 @@
#include "clang/AST/DeclVisitor.h"
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexSymbol.h"
-#include "llvm/Support/Casting.h"
using namespace clang;
using namespace index;
@@ -746,9 +745,9 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
if (const auto *CTD = llvm::dyn_cast<ClassTemplateDecl>(D))
for (auto *SD : CTD->specializations())
for (auto *RD : SD->redecls()) {
- auto *CTSD = cast<ClassTemplateSpecializationDecl>(RD);
+ auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD);
// For now we are only interested in instantiations with inheritance.
- if (!CTSD->hasDefinition() || CTSD->bases().empty())
+ if (!CTSD || !CTSD->hasDefinition() || CTSD->bases().empty())
continue;
// Explicit specialization is handled elsewhere
if (CTSD->isExplicitSpecialization())
diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp
index bb62b7d6f8d87..ee70bc05914ad 100644
--- a/clang/lib/Index/IndexTypeSourceInfo.cpp
+++ b/clang/lib/Index/IndexTypeSourceInfo.cpp
@@ -202,11 +202,12 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> {
auto QT = T->getReplacementType();
if (QT.isNull())
return true;
+ auto *CXXRD = QT->getAsCXXRecordDecl();
+ if (!CXXRD)
+ return true;
- IndexCtx.handleReference(QT->getAsCXXRecordDecl(), TL.getNameLoc(), Parent,
- ParentDC, SymbolRoleSet(), Relations);
-
- return true;
+ return IndexCtx.handleReference(CXXRD, TL.getNameLoc(), Parent, ParentDC,
+ SymbolRoleSet(), Relations);
}
bool VisitDeducedTemplateSpecializationTypeLoc(DeducedTemplateSpecializationTypeLoc TL) {
>From 178fe678a5bd3b9bcb59192be43df18a397fedb2 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Mon, 26 Jan 2026 14:47:35 +0100
Subject: [PATCH 07/14] Fixed infinite recursion
---
clang-tools-extra/clangd/XRefs.cpp | 3 ++
.../clangd/unittests/XRefsTests.cpp | 36 +++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 79decd35baadd..69f5dbc954692 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -306,6 +306,7 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
RelationsRequest Req;
Req.Predicate = Predicate;
+ llvm::DenseSet<SymbolID> SeenIDs;
llvm::DenseSet<SymbolID> RecursiveSearch = std::move(IDs);
std::vector<LocatedSymbol> Results;
@@ -313,6 +314,8 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
Req.Subjects = std::move(RecursiveSearch);
RecursiveSearch = {};
Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
+ if (!SeenIDs.insert(Object.ID).second)
+ return;
RecursiveSearch.insert(Object.ID);
auto DeclLoc =
indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath);
diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 9a382f4a8c257..2f52169214f7b 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1923,6 +1923,42 @@ TEST(FindImplementations, Inheritance) {
}
}
+TEST(FindImplementations, InheritanceRecursion) {
+ // Make sure inheritance is followed, but does not diverge.
+ llvm::StringRef Test = R"cpp(
+ template <int>
+ struct [[Ev^en]];
+
+ template <int>
+ struct [[Odd]];
+
+ template <>
+ struct Even<0> {
+ static const bool value = true;
+ };
+
+ template <>
+ struct Odd<0> {
+ static const bool value = false;
+ };
+
+ template <int I>
+ struct Even : Odd<I - 1> {};
+
+ template <int I>
+ struct Odd : Even<I - 1> {};
+
+ constexpr bool Answer = Even<42>::value;
+ )cpp";
+
+ Annotations Code(Test);
+ auto TU = TestTU::withCode(Code.code());
+ auto AST = TU.build();
+ auto Index = TU.index();
+ EXPECT_THAT(findImplementations(AST, Code.point(), Index.get()),
+ UnorderedPointwise(declRange(), Code.ranges()));
+}
+
TEST(FindImplementations, InheritanceObjC) {
llvm::StringRef Test = R"objc(
@interface $base^Base
>From 2e6a49c51127ed55970eaa5134d36ad3a13e02f0 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Tue, 27 Jan 2026 17:11:32 +0100
Subject: [PATCH 08/14] Added defRange matcher and renamed Queue - manual merge
---
clang-tools-extra/clangd/XRefs.cpp | 11 +++++------
clang-tools-extra/clangd/unittests/XRefsTests.cpp | 15 ++++++++++-----
2 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 69f5dbc954692..d4398a593d48e 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -307,16 +307,15 @@ std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs,
RelationsRequest Req;
Req.Predicate = Predicate;
llvm::DenseSet<SymbolID> SeenIDs;
- llvm::DenseSet<SymbolID> RecursiveSearch = std::move(IDs);
+ llvm::DenseSet<SymbolID> Queue = std::move(IDs);
std::vector<LocatedSymbol> Results;
-
- while (!RecursiveSearch.empty()) {
- Req.Subjects = std::move(RecursiveSearch);
- RecursiveSearch = {};
+ while (!Queue.empty()) {
+ Req.Subjects = std::move(Queue);
+ Queue = {};
Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
if (!SeenIDs.insert(Object.ID).second)
return;
- RecursiveSearch.insert(Object.ID);
+ Queue.insert(Object.ID);
auto DeclLoc =
indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath);
if (!DeclLoc) {
diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 2f52169214f7b..1b7b46c0d71b1 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -54,6 +54,11 @@ MATCHER(declRange, "") {
const Range &Range = ::testing::get<1>(arg);
return Sym.PreferredDeclaration.range == Range;
}
+MATCHER(defRange, "") {
+ const LocatedSymbol &Sym = ::testing::get<0>(arg);
+ const Range &Range = ::testing::get<1>(arg);
+ return Sym.Definition.value_or(Sym.PreferredDeclaration).range == Range;
+}
// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
@@ -1927,10 +1932,10 @@ TEST(FindImplementations, InheritanceRecursion) {
// Make sure inheritance is followed, but does not diverge.
llvm::StringRef Test = R"cpp(
template <int>
- struct [[Ev^en]];
+ struct Ev^en;
template <int>
- struct [[Odd]];
+ struct Odd;
template <>
struct Even<0> {
@@ -1943,10 +1948,10 @@ TEST(FindImplementations, InheritanceRecursion) {
};
template <int I>
- struct Even : Odd<I - 1> {};
+ struct [[Even]] : Odd<I - 1> {};
template <int I>
- struct Odd : Even<I - 1> {};
+ struct [[Odd]] : Even<I - 1> {};
constexpr bool Answer = Even<42>::value;
)cpp";
@@ -1956,7 +1961,7 @@ TEST(FindImplementations, InheritanceRecursion) {
auto AST = TU.build();
auto Index = TU.index();
EXPECT_THAT(findImplementations(AST, Code.point(), Index.get()),
- UnorderedPointwise(declRange(), Code.ranges()));
+ UnorderedPointwise(defRange(), Code.ranges()));
}
TEST(FindImplementations, InheritanceObjC) {
>From 3f37408884aac1f4fbf643c24fe8d0bc122467f3 Mon Sep 17 00:00:00 2001
From: Timon Ulrich <timon.ulrich at advantest.com>
Date: Wed, 28 Jan 2026 18:06:45 +0100
Subject: [PATCH 09/14] Fixed InheritanceRecursion test for newly found
instantiations
---
clang-tools-extra/clangd/unittests/XRefsTests.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 1b7b46c0d71b1..07434d26479c2 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -1948,12 +1948,12 @@ TEST(FindImplementations, InheritanceRecursion) {
};
template <int I>
- struct [[Even]] : Odd<I - 1> {};
+ struct [[[[Even]]]] : Odd<I - 1> {};
template <int I>
struct [[Odd]] : Even<I - 1> {};
- constexpr bool Answer = Even<42>::value;
+ constexpr bool Answer = Even<2>::value;
)cpp";
Annotations Code(Test);
>From 854c308fa718576a163c64b8beb09410057000bf Mon Sep 17 00:00:00 2001
From: timon-ul <timon.ulrich at advantest.com>
Date: Fri, 6 Mar 2026 14:30:20 +0100
Subject: [PATCH 10/14] Hide implicit template indexing behind indexing option
---
clang-tools-extra/clangd/XRefs.cpp | 1 +
clang-tools-extra/clangd/index/FileIndex.cpp | 1 +
clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp | 1 +
clang/lib/Index/IndexDecl.cpp | 3 +++
4 files changed, 6 insertions(+)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index d4398a593d48e..ff5e35e6bf62a 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1051,6 +1051,7 @@ findRefs(const llvm::ArrayRef<const NamedDecl *> TargetDecls, ParsedAST &AST,
IndexOpts.SystemSymbolFilter =
index::IndexingOptions::SystemSymbolFilterKind::All;
IndexOpts.IndexFunctionLocals = true;
+ IndexOpts.IndexImplicitInstantiation = true;
IndexOpts.IndexParametersInDeclarations = true;
IndexOpts.IndexTemplateParameters = true;
indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(),
diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp
index 2e005bfe3537e..4234886ce8eaf 100644
--- a/clang-tools-extra/clangd/index/FileIndex.cpp
+++ b/clang-tools-extra/clangd/index/FileIndex.cpp
@@ -65,6 +65,7 @@ SlabTuple indexSymbols(ASTContext &AST, Preprocessor &PP,
index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly;
// We index function-local classes and its member functions only.
IndexOpts.IndexFunctionLocals = true;
+ IndexOpts.IndexImplicitInstantiation = true;
if (IsIndexMainAST) {
// We only collect refs when indexing main AST.
CollectorOpts.RefFilter = RefKind::All;
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 94116fca3cbb2..2906b78f69471 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -310,6 +310,7 @@ class SymbolIndexActionFactory : public tooling::FrontendActionFactory {
IndexOpts.SystemSymbolFilter =
index::IndexingOptions::SystemSymbolFilterKind::All;
IndexOpts.IndexFunctionLocals = true;
+ IndexOpts.IndexImplicitInstantiation = true;
std::shared_ptr<include_cleaner::PragmaIncludes> PI =
std::make_shared<include_cleaner::PragmaIncludes>();
COpts.PragmaIncludes = PI.get();
diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp
index 35f8f7e43ed7d..cf8486298b993 100644
--- a/clang/lib/Index/IndexDecl.cpp
+++ b/clang/lib/Index/IndexDecl.cpp
@@ -738,6 +738,9 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> {
if (!shouldContinue)
return false;
+ if (!IndexCtx.shouldIndexImplicitInstantiation())
+ return true;
+
// Only check instantiation if D is canonical to prevent infinite cycling
if (D != D->getCanonicalDecl())
return true;
>From 744d0bbcda1b49526329a955bcacff2e83e97acd Mon Sep 17 00:00:00 2001
From: timon-ul <timon.ulrich at advantest.com>
Date: Mon, 9 Mar 2026 19:46:32 +0100
Subject: [PATCH 11/14] Adjusted behaviour for SymbolCollectorTest.Tempalte
---
clang-tools-extra/clangd/AST.cpp | 7 +++++++
clang-tools-extra/clangd/AST.h | 5 +++++
clang-tools-extra/clangd/CodeComplete.cpp | 4 ++--
.../clangd/unittests/SymbolCollectorTests.cpp | 14 ++++++++++----
4 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 3bcc89d360cdb..eaf3d9adc80d8 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -180,6 +180,13 @@ std::string getQualification(ASTContext &Context,
} // namespace
+bool isTemplateInstantiation(const NamedDecl *D) {
+ return isTemplateSpecializationKind(D,
+ TSK_ExplicitInstantiationDeclaration) ||
+ isTemplateSpecializationKind(D, TSK_ExplicitInstantiationDefinition) ||
+ isTemplateSpecializationKind(D, TSK_ImplicitInstantiation);
+}
+
bool isImplicitTemplateInstantiation(const NamedDecl *D) {
return isTemplateSpecializationKind(D, TSK_ImplicitInstantiation);
}
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 2bb4943b6de0b..c08e26472c528 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -138,6 +138,11 @@ std::string printType(const QualType QT, const DeclContext &CurContext,
llvm::StringRef Placeholder = "",
bool FullyQualify = false);
+/// Indicates if \p D is a template instantiation, implicit or explicit e.g.
+/// template <class T> struct vector {};
+/// template <> struct vector<bool>; // is an explicit instantiation.
+/// vector<int> v; // 'vector<int>' is an implicit instantiation.
+bool isTemplateInstantiation(const NamedDecl *D);
/// Indicates if \p D is a template instantiation implicitly generated by the
/// compiler, e.g.
/// template <class T> struct vector {};
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index f43b5e71a1dfa..a0e3a9b5353ee 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -2368,8 +2368,8 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) {
return ND.getDeclContext()->getDeclKind() == Decl::CXXRecord;
};
// We only complete symbol's name, which is the same as the name of the
- // *primary* template in case of template specializations.
- if (isExplicitTemplateSpecialization(&ND))
+ // *primary* template in case of template specializations or instantiation.
+ if (isExplicitTemplateSpecialization(&ND) || isTemplateInstantiation(&ND))
return false;
// Category decls are not useful on their own outside the interface or
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 2906b78f69471..109585d072397 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -486,13 +486,11 @@ TEST_F(SymbolCollectorTest, FileLocal) {
TEST_F(SymbolCollectorTest, Template) {
Annotations Header(R"(
- // Primary template and explicit specialization are indexed, instantiation
- // is not.
template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;};
template <> struct $specdecl[[Tmpl]]<int, bool> {};
template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {};
- extern template struct Tmpl<float, bool>;
- template struct Tmpl<double, bool>;
+ extern template struct $extinst[[Tmpl]]<float, bool>;
+ template struct $inst[[Tmpl]]<double, bool>;
)");
runSymbolCollector(Header.code(), /*Main=*/"");
EXPECT_THAT(Symbols,
@@ -503,7 +501,15 @@ TEST_F(SymbolCollectorTest, Template) {
forCodeCompletion(false)),
AllOf(qName("Tmpl"), declRange(Header.range("partspecdecl")),
forCodeCompletion(false)),
+ AllOf(qName("Tmpl"), declRange(Header.range("extinst")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl"), declRange(Header.range("inst")),
+ forCodeCompletion(false)),
AllOf(qName("Tmpl::x"), declRange(Header.range("xdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl<float, bool>::x"), declRange(Header.range("xdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl<double, bool>::x"), declRange(Header.range("xdecl")),
forCodeCompletion(false))));
}
>From 121265f34cc80725b7caadd96c26141dcb436b09 Mon Sep 17 00:00:00 2001
From: timon-ul <timon.ulrich at advantest.com>
Date: Mon, 9 Mar 2026 21:46:05 +0100
Subject: [PATCH 12/14] Prevent doc creation for implicit instantiations
---
clang-tools-extra/clangd/index/SymbolCollector.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 30f34324f5e58..4c45a0479a2ba 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -1118,6 +1118,11 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID,
if (ND.getAvailability() == AR_Deprecated)
S.Flags |= Symbol::Deprecated;
+ if (isImplicitTemplateInstantiation(&ND)) {
+ Symbols.insert(S);
+ return Symbols.find(S.ID);
+ }
+
// Add completion info.
// FIXME: we may want to choose a different redecl, or combine from several.
assert(ASTCtx && PP && "ASTContext and Preprocessor must be set.");
>From e29de3d462f7d8e2c25880589e570a473d0b4c14 Mon Sep 17 00:00:00 2001
From: timon-ul <timon.ulrich at advantest.com>
Date: Tue, 10 Mar 2026 18:44:43 +0100
Subject: [PATCH 13/14] Added test for libIndex new behaviour
---
clang/unittests/Index/IndexTests.cpp | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/clang/unittests/Index/IndexTests.cpp b/clang/unittests/Index/IndexTests.cpp
index 6df4b577d98a0..2926f206a3207 100644
--- a/clang/unittests/Index/IndexTests.cpp
+++ b/clang/unittests/Index/IndexTests.cpp
@@ -16,6 +16,7 @@
#include "clang/Index/IndexDataConsumer.h"
#include "clang/Index/IndexSymbol.h"
#include "clang/Index/IndexingAction.h"
+#include "clang/Index/IndexingOptions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/StringRef.h"
@@ -259,6 +260,28 @@ TEST(IndexTest, IndexExplicitTemplateInstantiation) {
DeclAt(Position(3, 12))))));
}
+TEST(IndexTest, IndexImplicitTemplateInstantiation) {
+ std::string Code = R"cpp(
+ struct Seagulls {};
+ struct Cry {};
+
+ template <typename... T>
+ struct The : T... {};
+
+ struct When : The<Seagulls,Cry> {};
+ )cpp";
+ auto Index = std::make_shared<Indexer>();
+ IndexingOptions Opts;
+ Opts.IndexImplicitInstantiation = true;
+ tooling::runToolOnCode(std::make_unique<IndexAction>(Index, Opts), Code);
+ EXPECT_THAT(
+ Index->Symbols,
+ AllOf(Contains(AllOf(QName("Seagulls"), WrittenAt(Position(6, 18)),
+ DeclAt(Position(2, 12)))),
+ Contains(AllOf(QName("Cry"), WrittenAt(Position(6, 18)),
+ DeclAt(Position(3, 12))))));
+}
+
TEST(IndexTest, IndexTemplateInstantiationPartial) {
std::string Code = R"cpp(
template <typename T1, typename T2>
>From c939b39759d350ff6dbb94b70654bda896d7d9d8 Mon Sep 17 00:00:00 2001
From: timon-ul <timon.ulrich at advantest.com>
Date: Tue, 10 Mar 2026 19:52:30 +0100
Subject: [PATCH 14/14] Formatted test
---
.../clangd/unittests/SymbolCollectorTests.cpp | 37 ++++++++++---------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
index 109585d072397..3b0a3022f6143 100644
--- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
@@ -493,24 +493,25 @@ TEST_F(SymbolCollectorTest, Template) {
template struct $inst[[Tmpl]]<double, bool>;
)");
runSymbolCollector(Header.code(), /*Main=*/"");
- EXPECT_THAT(Symbols,
- UnorderedElementsAre(
- AllOf(qName("Tmpl"), declRange(Header.range()),
- forCodeCompletion(true)),
- AllOf(qName("Tmpl"), declRange(Header.range("specdecl")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl"), declRange(Header.range("partspecdecl")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl"), declRange(Header.range("extinst")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl"), declRange(Header.range("inst")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl::x"), declRange(Header.range("xdecl")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl<float, bool>::x"), declRange(Header.range("xdecl")),
- forCodeCompletion(false)),
- AllOf(qName("Tmpl<double, bool>::x"), declRange(Header.range("xdecl")),
- forCodeCompletion(false))));
+ EXPECT_THAT(
+ Symbols,
+ UnorderedElementsAre(
+ AllOf(qName("Tmpl"), declRange(Header.range()),
+ forCodeCompletion(true)),
+ AllOf(qName("Tmpl"), declRange(Header.range("specdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl"), declRange(Header.range("partspecdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl"), declRange(Header.range("extinst")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl"), declRange(Header.range("inst")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl::x"), declRange(Header.range("xdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl<float, bool>::x"), declRange(Header.range("xdecl")),
+ forCodeCompletion(false)),
+ AllOf(qName("Tmpl<double, bool>::x"),
+ declRange(Header.range("xdecl")), forCodeCompletion(false))));
}
TEST_F(SymbolCollectorTest, templateArgs) {
More information about the cfe-commits
mailing list