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 <[email protected]> Date: Sat, 17 Jan 2026 03:16:06 +0100 Subject: [PATCH 01/18] 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 <[email protected]> Date: Sat, 17 Jan 2026 03:20:22 +0100 Subject: [PATCH 02/18] 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 <[email protected]> Date: Wed, 21 Jan 2026 23:20:57 +0100 Subject: [PATCH 03/18] 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 <[email protected]> Date: Thu, 22 Jan 2026 00:05:17 +0100 Subject: [PATCH 04/18] 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 <[email protected]> Date: Thu, 22 Jan 2026 00:28:59 +0100 Subject: [PATCH 05/18] 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 <[email protected]> Date: Thu, 22 Jan 2026 11:05:50 +0100 Subject: [PATCH 06/18] 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 <[email protected]> Date: Mon, 26 Jan 2026 14:47:35 +0100 Subject: [PATCH 07/18] 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 <[email protected]> Date: Tue, 27 Jan 2026 17:11:32 +0100 Subject: [PATCH 08/18] 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 <[email protected]> Date: Wed, 28 Jan 2026 18:06:45 +0100 Subject: [PATCH 09/18] 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 <[email protected]> Date: Fri, 6 Mar 2026 14:30:20 +0100 Subject: [PATCH 10/18] 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 <[email protected]> Date: Mon, 9 Mar 2026 19:46:32 +0100 Subject: [PATCH 11/18] 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 <[email protected]> Date: Mon, 9 Mar 2026 21:46:05 +0100 Subject: [PATCH 12/18] 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 <[email protected]> Date: Tue, 10 Mar 2026 18:44:43 +0100 Subject: [PATCH 13/18] 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 <[email protected]> Date: Tue, 10 Mar 2026 19:52:30 +0100 Subject: [PATCH 14/18] 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) { >From e1a2e0508356978d922816d7634cc8e33186c7cb Mon Sep 17 00:00:00 2001 From: timon-ul <[email protected]> Date: Wed, 11 Mar 2026 22:36:46 +0100 Subject: [PATCH 15/18] Cleanup --- .../clangd/index/IndexAction.cpp | 1 + .../clangd/index/SymbolCollector.cpp | 20 +++++++++---------- clang/lib/Index/IndexDecl.cpp | 4 ++-- clang/lib/Index/IndexTypeSourceInfo.cpp | 3 +++ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/clang-tools-extra/clangd/index/IndexAction.cpp b/clang-tools-extra/clangd/index/IndexAction.cpp index 09943400f6d86..feb8b7a8dc6fb 100644 --- a/clang-tools-extra/clangd/index/IndexAction.cpp +++ b/clang-tools-extra/clangd/index/IndexAction.cpp @@ -224,6 +224,7 @@ std::unique_ptr<FrontendAction> createStaticIndexingAction( index::IndexingOptions::SystemSymbolFilterKind::All; // We index function-local classes and its member functions only. IndexOpts.IndexFunctionLocals = true; + IndexOpts.IndexImplicitInstantiation = true; // We need to delay indexing so instantiations of function bodies become // available, this is so we can find constructor calls through `make_unique`. IndexOpts.DeferIndexingToEndOfTranslationUnit = true; diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 4c45a0479a2ba..fcc2aae755ecf 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -1118,7 +1118,10 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, if (ND.getAvailability() == AR_Deprecated) S.Flags |= Symbol::Deprecated; - if (isImplicitTemplateInstantiation(&ND)) { + // Computing doc comments is expensive, so if we can skip it we should do so. + if (isImplicitTemplateInstantiation(&ND) || + (!(S.Flags & Symbol::IndexedForCodeCompletion) && + !Opts.StoreAllDocumentation)) { Symbols.insert(S); return Symbols.find(S.ID); } @@ -1139,21 +1142,16 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, DocComment = getDocComment(Ctx, SymbolCompletion, /*CommentsFromHeaders=*/true); Documentation = formatDocumentation(*CCS, DocComment); + if (!DocComment.empty()) + S.Flags |= Symbol::HasDocComment; + S.Documentation = Documentation; } - const auto UpdateDoc = [&] { - if (!AlreadyHasDoc) { - if (!DocComment.empty()) - S.Flags |= Symbol::HasDocComment; - S.Documentation = Documentation; - } - }; + if (!(S.Flags & Symbol::IndexedForCodeCompletion)) { - if (Opts.StoreAllDocumentation) - UpdateDoc(); Symbols.insert(S); return Symbols.find(S.ID); } - UpdateDoc(); + std::string Signature; std::string SnippetSuffix; getSignature(*CCS, &Signature, &SnippetSuffix, SymbolCompletion.Kind, diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp index cf8486298b993..b3a55309ad892 100644 --- a/clang/lib/Index/IndexDecl.cpp +++ b/clang/lib/Index/IndexDecl.cpp @@ -734,8 +734,8 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> { indexTemplateParameters(Params, Parent); } - bool shouldContinue = Visit(Parent); - if (!shouldContinue) + bool ShouldContinue = Visit(Parent); + if (!ShouldContinue) return false; if (!IndexCtx.shouldIndexImplicitInstantiation()) diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp index ee70bc05914ad..1e1c7ae0330df 100644 --- a/clang/lib/Index/IndexTypeSourceInfo.cpp +++ b/clang/lib/Index/IndexTypeSourceInfo.cpp @@ -196,6 +196,9 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> { bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL, bool TraverseQualifier) { + // TODO: For now if we are a templated field and the substituted type is of + // form `A<B>`, we will only record a reference to `A`, but it is reasonable + // to also expect a reference to `B` to be recorded. const auto *T = TL.getTypePtr(); if (!T) return true; >From 8472ec826d0f929b707e4035e5a4175903808902 Mon Sep 17 00:00:00 2001 From: timon-ul <[email protected]> Date: Fri, 27 Mar 2026 03:38:15 +0100 Subject: [PATCH 16/18] Clean up and review comments --- clang-tools-extra/clangd/AST.cpp | 10 ++++++- clang-tools-extra/clangd/AST.h | 3 +- clang-tools-extra/clangd/CodeComplete.cpp | 2 +- .../clangd/index/SymbolCollector.cpp | 7 ++--- .../clangd/unittests/SymbolCollectorTests.cpp | 30 +++++++++++++++---- clang/lib/Index/IndexDecl.cpp | 4 +-- clang/lib/Index/IndexTypeSourceInfo.cpp | 6 ++-- clang/lib/Index/IndexingContext.cpp | 15 ++++++---- 8 files changed, 54 insertions(+), 23 deletions(-) diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index eaf3d9adc80d8..727a65b2e8867 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -180,7 +180,15 @@ std::string getQualification(ASTContext &Context, } // namespace -bool isTemplateInstantiation(const NamedDecl *D) { +bool isTemplateInstantiationScope(const NamedDecl *D) { + // Fields and Methods don't know about template instantiations, so we have to + // ask their parent. + if (auto *FD = dyn_cast<FieldDecl>(D)) + if (auto *RD = FD->getParent()) + D = RD; + if (auto *MD = dyn_cast<CXXMethodDecl>(D)) + if (auto *RD = MD->getParent()) + D = RD; return isTemplateSpecializationKind(D, TSK_ExplicitInstantiationDeclaration) || isTemplateSpecializationKind(D, TSK_ExplicitInstantiationDefinition) || diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index c08e26472c528..d9636b1b4b4a1 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -142,7 +142,8 @@ std::string printType(const QualType QT, const DeclContext &CurContext, /// 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); +/// Also returns true for methods and fields inside of an instantiation. +bool isTemplateInstantiationScope(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 a0e3a9b5353ee..56c3678c0ab74 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -2369,7 +2369,7 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) { }; // We only complete symbol's name, which is the same as the name of the // *primary* template in case of template specializations or instantiation. - if (isExplicitTemplateSpecialization(&ND) || isTemplateInstantiation(&ND)) + if (isExplicitTemplateSpecialization(&ND) || isTemplateInstantiationScope(&ND)) return false; // Category decls are not useful on their own outside the interface or diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index fcc2aae755ecf..f85a304a2bdf6 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -34,6 +34,7 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/Specifiers.h" #include "clang/Index/IndexSymbol.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/Token.h" @@ -679,9 +680,7 @@ bool SymbolCollector::handleDeclOccurrence( 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()) { + if (isTemplateInstantiationScope(ND)) { CollectRef = false; } // Unlike other fields, e.g. Symbols (which use spelling locations), we use @@ -1119,7 +1118,7 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, S.Flags |= Symbol::Deprecated; // Computing doc comments is expensive, so if we can skip it we should do so. - if (isImplicitTemplateInstantiation(&ND) || + if (isTemplateInstantiationScope(&ND) || (!(S.Flags & Symbol::IndexedForCodeCompletion) && !Opts.StoreAllDocumentation)) { Symbols.insert(S); diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 3b0a3022f6143..90fb5a5b08aac 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -486,32 +486,50 @@ TEST_F(SymbolCollectorTest, FileLocal) { TEST_F(SymbolCollectorTest, Template) { Annotations Header(R"( - template <class T, class U> struct [[Tmpl]] {T $xdecl[[x]] = 0;}; + template <class T, class U> struct [[Tmpl]] { + //doc only for primary x + T $xdecl[[x]] = 0; + //doc only for primary y + T $ydecl[[y]](); + //doc only for primary z + struct $zdecl[[z]] {}; + }; template <> struct $specdecl[[Tmpl]]<int, bool> {}; template <class U> struct $partspecdecl[[Tmpl]]<bool, U> {}; extern template struct $extinst[[Tmpl]]<float, bool>; template struct $inst[[Tmpl]]<double, bool>; )"); + CollectorOpts.StoreAllDocumentation = true; runSymbolCollector(Header.code(), /*Main=*/""); EXPECT_THAT( Symbols, UnorderedElementsAre( AllOf(qName("Tmpl"), declRange(Header.range()), forCodeCompletion(true)), + AllOf(qName("Tmpl::x"), declRange(Header.range("xdecl")), + doc("doc only for primary x"), forCodeCompletion(false)), + AllOf(qName("Tmpl::y"), declRange(Header.range("ydecl")), + doc("doc only for primary y"), forCodeCompletion(false)), + AllOf(qName("Tmpl::z"), declRange(Header.range("zdecl")), + doc("doc only for primary z"), forCodeCompletion(false)), 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")), + doc(""), forCodeCompletion(false)), + AllOf(qName("Tmpl<float, bool>::y"), declRange(Header.range("ydecl")), + doc(""), forCodeCompletion(false)), + AllOf(qName("Tmpl"), declRange(Header.range("inst")), forCodeCompletion(false)), AllOf(qName("Tmpl<double, bool>::x"), - declRange(Header.range("xdecl")), forCodeCompletion(false)))); + declRange(Header.range("xdecl")), doc(""), + forCodeCompletion(false)), + AllOf(qName("Tmpl<double, bool>::y"), + declRange(Header.range("ydecl")), doc(""), + forCodeCompletion(false)))); } TEST_F(SymbolCollectorTest, templateArgs) { diff --git a/clang/lib/Index/IndexDecl.cpp b/clang/lib/Index/IndexDecl.cpp index b3a55309ad892..64b05960b7d01 100644 --- a/clang/lib/Index/IndexDecl.cpp +++ b/clang/lib/Index/IndexDecl.cpp @@ -752,8 +752,8 @@ class IndexingDeclVisitor : public ConstDeclVisitor<IndexingDeclVisitor, bool> { // For now we are only interested in instantiations with inheritance. if (!CTSD || !CTSD->hasDefinition() || CTSD->bases().empty()) continue; - // Explicit specialization is handled elsewhere - if (CTSD->isExplicitSpecialization()) + // Explicit instantiations and specializations are handled elsewhere + if (CTSD->isExplicitInstantiationOrSpecialization()) continue; Visit(RD); } diff --git a/clang/lib/Index/IndexTypeSourceInfo.cpp b/clang/lib/Index/IndexTypeSourceInfo.cpp index 1e1c7ae0330df..ee7e38cb36bdc 100644 --- a/clang/lib/Index/IndexTypeSourceInfo.cpp +++ b/clang/lib/Index/IndexTypeSourceInfo.cpp @@ -196,15 +196,15 @@ class TypeIndexer : public RecursiveASTVisitor<TypeIndexer> { bool TraverseSubstTemplateTypeParmTypeLoc(SubstTemplateTypeParmTypeLoc TL, bool TraverseQualifier) { - // TODO: For now if we are a templated field and the substituted type is of - // form `A<B>`, we will only record a reference to `A`, but it is reasonable - // to also expect a reference to `B` to be recorded. const auto *T = TL.getTypePtr(); if (!T) return true; auto QT = T->getReplacementType(); if (QT.isNull()) return true; + // TODO: For now if we are a templated field and the substituted type is of + // form `A<B>`, we will only record a reference to `A`, but it is reasonable + // to also expect a reference to `B` to be recorded. auto *CXXRD = QT->getAsCXXRecordDecl(); if (!CXXRD) return true; diff --git a/clang/lib/Index/IndexingContext.cpp b/clang/lib/Index/IndexingContext.cpp index c6843cf46cf8e..fc2e3758fe0ac 100644 --- a/clang/lib/Index/IndexingContext.cpp +++ b/clang/lib/Index/IndexingContext.cpp @@ -402,11 +402,16 @@ bool IndexingContext::handleDeclOccurrence(const Decl *D, SourceLocation Loc, if (!OrigD) OrigD = D; - if (isTemplateImplicitInstantiation(D) && IsRef) { - D = adjustTemplateImplicitInstantiation(D); - if (!D) - return true; - assert(!isTemplateImplicitInstantiation(D)); + if (isTemplateImplicitInstantiation(D)) { + if (IsRef) { + D = adjustTemplateImplicitInstantiation(D); + if (!D) + return true; + assert(!isTemplateImplicitInstantiation(D)); + } else { + if (!shouldIndexImplicitInstantiation()) + return true; + } } if (IsRef) >From ef3984285d61f4072cfaca01cb9d1b9fc0d55eb5 Mon Sep 17 00:00:00 2001 From: timon-ul <[email protected]> Date: Fri, 27 Mar 2026 03:41:07 +0100 Subject: [PATCH 17/18] formatting --- clang-tools-extra/clangd/CodeComplete.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 56c3678c0ab74..279133f178603 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -2369,7 +2369,8 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) { }; // We only complete symbol's name, which is the same as the name of the // *primary* template in case of template specializations or instantiation. - if (isExplicitTemplateSpecialization(&ND) || isTemplateInstantiationScope(&ND)) + if (isExplicitTemplateSpecialization(&ND) || + isTemplateInstantiationScope(&ND)) return false; // Category decls are not useful on their own outside the interface or >From 485cb64ddacef859c9e9af5801dd045e03e96ecc Mon Sep 17 00:00:00 2001 From: timon-ul <[email protected]> Date: Fri, 27 Mar 2026 13:15:05 +0100 Subject: [PATCH 18/18] Fixed documentation error --- clang-tools-extra/clangd/AST.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index d9636b1b4b4a1..beba6b68da6ee 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -140,8 +140,8 @@ std::string printType(const QualType QT, const DeclContext &CurContext, /// 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. +/// template struct vector<bool>; // is an explicit instantiation. +/// vector<int> v; // is an implicit instantiation. /// Also returns true for methods and fields inside of an instantiation. bool isTemplateInstantiationScope(const NamedDecl *D); /// Indicates if \p D is a template instantiation implicitly generated by the _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
