[clang-tools-extra] Calculate symbol tags Overrides and Implements (PR #181997)
Dimitri Ratz via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 18 04:47:59 PST 2026
https://github.com/ratzdi updated https://github.com/llvm/llvm-project/pull/181997
>From 36ca87786fded14d2271cccc732778f249befb63 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 28 Nov 2025 14:01:25 +0100
Subject: [PATCH 01/15] Fill symbol tags for the response TypeHierarchyItem of
prepareTypeHierarchy.
---
clang-tools-extra/clangd/FindSymbols.cpp | 14 ++++++++++++++
clang-tools-extra/clangd/FindSymbols.h | 3 +++
clang-tools-extra/clangd/Protocol.cpp | 2 ++
clang-tools-extra/clangd/XRefs.cpp | 1 +
clang-tools-extra/clangd/test/type-hierarchy.test | 9 +++++++++
5 files changed, 29 insertions(+)
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 243746056aed0..3a14fe060fbbb 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -228,6 +228,20 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
return Tags;
}
+std::vector<SymbolTag> getSymbolTags(const Symbol &S) {
+ std::vector<SymbolTag> Tags;
+
+ if (S.Flags & Symbol::Deprecated)
+ Tags.push_back(SymbolTag::Deprecated);
+
+ if (S.Definition)
+ Tags.push_back(SymbolTag::Definition);
+ else
+ Tags.push_back(SymbolTag::Declaration);
+
+ return Tags;
+}
+
namespace {
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
struct ScoredSymbolGreater {
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 97b99af4f35e6..4847ea359e9ef 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -69,6 +69,9 @@ SymbolTags computeSymbolTags(const NamedDecl &ND);
/// \p ND The declaration to get tags for.
std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
+/// Returns the symbol tags for an index `Symbol`.
+std::vector<SymbolTag> getSymbolTags(const Symbol &S);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 9926f2dd63de5..5ff92fca933ff 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1437,6 +1437,8 @@ llvm::json::Value toJSON(const TypeHierarchyItem &I) {
if (I.detail)
Result["detail"] = I.detail;
+ if(!I.tags.empty())
+ Result["tags"] = I.tags;
return std::move(Result);
}
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 8a24d19a7d129..6747b165cda5a 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1864,6 +1864,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
HI.name = std::string(S.Name);
HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
+ HI.tags = getSymbolTags(S);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
// (https://github.com/clangd/clangd/issues/59).
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index a5f13ab13d0b3..918a37a74098c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -44,6 +44,9 @@
# CHECK-NEXT: "line": 2
# CHECK-NEXT: }
# CHECK-NEXT: },
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 19
+# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ]
@@ -85,6 +88,9 @@
# CHECK-NEXT: "line": 1
# CHECK-NEXT: }
# CHECK-NEXT: },
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 19
+# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ]
@@ -136,6 +142,9 @@
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 19
+# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ]
>From 4b09729b54a6e0565d4e6e1da1be52e81f824b9d Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 1 Dec 2025 11:34:51 +0100
Subject: [PATCH 02/15] Fill symbol tags into the object SymbolInformation, and
on getting workspace symbols.
---
clang-tools-extra/clangd/FindSymbols.cpp | 1 +
clang-tools-extra/clangd/Protocol.cpp | 2 ++
clang-tools-extra/clangd/test/symbols.test | 5 ++++-
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 3a14fe060fbbb..5b0bb4a43f883 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -373,6 +373,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
Info.score = Relevance.NameMatch > std::numeric_limits<float>::epsilon()
? Score / Relevance.NameMatch
: QualScore;
+ Info.tags = getSymbolTags(Sym);
Top.push({Score, std::move(Info)});
});
for (auto &R : std::move(Top).items())
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 5ff92fca933ff..55c06be0ae21c 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -859,6 +859,8 @@ llvm::json::Value toJSON(const SymbolInformation &P) {
};
if (P.score)
O["score"] = *P.score;
+ if(!P.tags.empty())
+ O["tags"] = P.tags;
return std::move(O);
}
diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index a16a226e48c05..4f0d7b8b08569 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -24,7 +24,10 @@
# CHECK-NEXT: "uri": "file://{{.*}}/vector.h"
# CHECK-NEXT: },
# CHECK-NEXT: "name": "vector",
-# CHECK-NEXT: "score": {{.*}}
+# CHECK-NEXT: "score": {{.*}},
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 18
+# CHECK-NEXT: ]
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT:}
>From 92c1162ec92f654fd8b0c01ca9e17ea63e8f7b40 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 1 Dec 2025 12:03:35 +0100
Subject: [PATCH 03/15] Minor improvements.
---
clang-tools-extra/clangd/FindSymbols.cpp | 3 +++
clang-tools-extra/clangd/FindSymbols.h | 6 +++++-
clang-tools-extra/clangd/XRefs.cpp | 2 --
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 5b0bb4a43f883..7fe91f883c79f 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -13,7 +13,10 @@
#include "Quality.h"
#include "SourceCode.h"
#include "index/Index.h"
+#include "index/Symbol.h"
+#include "index/SymbolLocation.h"
#include "support/Logger.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/DeclFriend.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Index/IndexSymbol.h"
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 4847ea359e9ef..169efa9a84584 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -14,13 +14,17 @@
#include "Protocol.h"
#include "index/Symbol.h"
-#include "clang/AST/Decl.h"
#include "llvm/ADT/StringRef.h"
+#include "clang/AST/Decl.h"
namespace clang {
+class NamedDecl;
+
namespace clangd {
class ParsedAST;
class SymbolIndex;
+struct Symbol;
+struct SymbolLocation;
/// A bitmask type representing symbol tags supported by LSP.
/// \see
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 6747b165cda5a..5a53e387ac545 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1890,8 +1890,6 @@ symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) {
if (!Result)
return Result;
Result->data = S.ID.str();
- if (S.Flags & Symbol::Deprecated)
- Result->tags.push_back(SymbolTag::Deprecated);
return Result;
}
>From 9003cca1755f02302b1050495e47a737c8d43a4b Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 3 Dec 2025 13:09:58 +0100
Subject: [PATCH 04/15] Set the detail field of HierarchyItem.
---
clang-tools-extra/clangd/XRefs.cpp | 2 +-
clang-tools-extra/clangd/test/call-hierarchy.test | 1 +
clang-tools-extra/clangd/test/type-hierarchy-ext.test | 3 +++
clang-tools-extra/clangd/test/type-hierarchy.test | 1 +
4 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 5a53e387ac545..7be68c5d1e350 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1810,7 +1810,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
HierarchyItem HI;
HI.name = printName(Ctx, ND);
- // FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
+ HI.detail = printQualifiedName(ND);
HI.kind = SK;
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index 6548ea0068a8d..44c89d22d9c85 100644
--- a/clang-tools-extra/clangd/test/call-hierarchy.test
+++ b/clang-tools-extra/clangd/test/call-hierarchy.test
@@ -9,6 +9,7 @@
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": "{{.*}}",
+# CHECK-NEXT: "detail": "callee",
# CHECK-NEXT: "kind": 12,
# CHECK-NEXT: "name": "callee",
# CHECK-NEXT: "range": {
diff --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
index 8d1a5dc31da0f..983c7538088bf 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
@@ -52,6 +52,7 @@
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "8A991335E4E67D08"
# CHECK-NEXT: },
+# CHECK-NEXT: "detail": "Child2",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child2",
# CHECK-NEXT: "parents": [
@@ -65,6 +66,7 @@
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: },
+# CHECK-NEXT: "detail": "Child1",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "parents": [
@@ -73,6 +75,7 @@
# CHECK-NEXT: "parents": [],
# CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: },
+# CHECK-NEXT: "detail": "Parent",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Parent",
# CHECK-NEXT: "parents": [],
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index 918a37a74098c..d1dda4b92c29c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -22,6 +22,7 @@
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "8A991335E4E67D08"
# CHECK-NEXT: },
+# CHECK-NEXT: "detail": "Child2",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child2",
# CHECK-NEXT: "range": {
>From 195604ec4220c7cd81ca55a1eff0a62310e84caa Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 12 Dec 2025 12:17:56 +0100
Subject: [PATCH 05/15] Collect symbols tags from AST in the method
incomingCalls.
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 2 +-
clang-tools-extra/clangd/ClangdServer.cpp | 12 ++--
clang-tools-extra/clangd/ClangdServer.h | 5 +-
clang-tools-extra/clangd/XRefs.cpp | 57 +++++++++++++++++--
clang-tools-extra/clangd/XRefs.h | 3 +-
.../clangd/unittests/CallHierarchyTests.cpp | 56 +++++++++---------
6 files changed, 93 insertions(+), 42 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 761b07eceec83..3fa217ff833bb 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1389,7 +1389,7 @@ void ClangdLSPServer::onPrepareCallHierarchy(
void ClangdLSPServer::onCallHierarchyIncomingCalls(
const CallHierarchyIncomingCallsParams &Params,
Callback<std::vector<CallHierarchyIncomingCall>> Reply) {
- Server->incomingCalls(Params.item, std::move(Reply));
+ Server->incomingCalls(Params.item.uri.file(), Params.item, std::move(Reply));
}
void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index f1a87dd12d905..21d69b99ad8d4 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -909,12 +909,16 @@ void ClangdServer::prepareCallHierarchy(
}
void ClangdServer::incomingCalls(
+ PathRef File,
const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>> CB) {
- WorkScheduler->run("Incoming Calls", "",
- [CB = std::move(CB), Item, this]() mutable {
- CB(clangd::incomingCalls(Item, Index));
- });
+ auto Action = [Item, CB = std::move(CB), this](
+ llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::incomingCalls(Item, Index, InpAST->AST));
+ };
+ WorkScheduler->runWithAST("Incoming Calls", File, std::move(Action));
}
void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 3ffaf67553dce..ae7e25e2b6db6 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -299,8 +299,9 @@ class ClangdServer {
Callback<std::vector<CallHierarchyItem>> CB);
/// Resolve incoming calls for a given call hierarchy item.
- void incomingCalls(const CallHierarchyItem &Item,
- Callback<std::vector<CallHierarchyIncomingCall>>);
+ void incomingCalls(PathRef File,
+ const CallHierarchyItem &Item,
+ Callback<std::vector<CallHierarchyIncomingCall>> CB);
/// Resolve outgoing calls for a given call hierarchy item.
void outgoingCalls(const CallHierarchyItem &Item,
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 7be68c5d1e350..f326c3ecea92d 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -2341,6 +2341,36 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
Item.uri.file());
}
+// Tries to find a NamedDecl in the AST that matches the given Symbol.
+// Returns nullptr if the symbol is not found in the current AST.
+const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
+ const ParsedAST &AST) {
+ // Try to convert the symbol to a location and find the decl at that location
+ auto SymLoc = symbolToLocation(Sym, AST.tuPath());
+ if (!SymLoc)
+ return nullptr;
+
+ // Check if the symbol location is in the main file
+ if (SymLoc->uri.file() != AST.tuPath())
+ return nullptr;
+
+ // Convert LSP position to source location
+ const auto &SM = AST.getSourceManager();
+ auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
+ if (!CurLoc) {
+ llvm::consumeError(CurLoc.takeError());
+ return nullptr;
+ }
+
+ // Get all decls at this location
+ auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
+ if (Decls.empty())
+ return nullptr;
+
+ // Return the first decl (usually the most specific one)
+ return Decls[0];
+}
+
std::vector<CallHierarchyItem>
prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
std::vector<CallHierarchyItem> Result;
@@ -2368,8 +2398,10 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
}
std::vector<CallHierarchyIncomingCall>
-incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
+incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST) {
std::vector<CallHierarchyIncomingCall> Results;
+
if (!Index || Item.data.empty())
return Results;
auto ID = SymbolID::fromStr(Item.data);
@@ -2413,14 +2445,27 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
auto It = CallsIn.find(Caller.ID);
assert(It != CallsIn.end());
- if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
+ if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) {
+ if (auto CHI = declToCallHierarchyItem(*ND, AST.tuPath())) {
+ std::vector<Range> FromRanges;
+ for (const Location &L : It->second) {
+ if (L.uri != CHI->uri) {
+ // Call location not in same file as caller.
+ // This can happen in some edge cases. There's not much we can do,
+ // since the protocol only allows returning ranges interpreted as
+ // being in the caller's file.
+ continue;
+ }
+ FromRanges.push_back(L.range);
+ }
+ Results.push_back(CallHierarchyIncomingCall{
+ std::move(*CHI), std::move(FromRanges), MightNeverCall});
+ }
+ } else if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
+ // Fallback to using symbol if NamedDecl is not available
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != CHI->uri) {
- // Call location not in same file as caller.
- // This can happen in some edge cases. There's not much we can do,
- // since the protocol only allows returning ranges interpreted as
- // being in the caller's file.
continue;
}
FromRanges.push_back(L.range);
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 247e52314c3f9..1019fa189a613 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -148,7 +148,8 @@ std::vector<CallHierarchyItem>
prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
std::vector<CallHierarchyIncomingCall>
-incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
+incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST);
std::vector<CallHierarchyOutgoingCall>
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 9859577c7cf7e..f5e9983aa70ec 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -89,12 +89,12 @@ TEST(CallHierarchy, IncomingOneFileCpp) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(
IncomingLevel1,
ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
iFromRanges(Source.range("Callee")))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(
IncomingLevel2,
ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
@@ -103,13 +103,13 @@ TEST(CallHierarchy, IncomingOneFileCpp) {
AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
iFromRanges(Source.range("Caller1C")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+ auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(
IncomingLevel3,
ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
iFromRanges(Source.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+ auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
}
@@ -137,12 +137,12 @@ TEST(CallHierarchy, IncomingOneFileObjC) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(AllOf(withName("caller1"),
withDetail("MyClass::caller1"))),
iFromRanges(Source.range("Callee")))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel2,
ElementsAre(AllOf(from(AllOf(withName("caller2"),
withDetail("MyClass::caller2"))),
@@ -152,13 +152,13 @@ TEST(CallHierarchy, IncomingOneFileObjC) {
withDetail("MyClass::caller3"))),
iFromRanges(Source.range("Caller1C")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+ auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel3,
ElementsAre(AllOf(from(AllOf(withName("caller3"),
withDetail("MyClass::caller3"))),
iFromRanges(Source.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+ auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
}
@@ -184,18 +184,18 @@ TEST(CallHierarchy, IncomingIncludeOverrides) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(AllOf(withName("Func"),
withDetail("Implementation::Func"))),
iFromRanges(Source.range("Callee")))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(
IncomingLevel2,
ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail("Test"))),
iFromRanges(Source.range("FuncCall")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+ auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel3, IsEmpty());
}
@@ -221,13 +221,13 @@ TEST(CallHierarchy, MainFileOnlyRef) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(
IncomingLevel1,
ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
iFromRanges(Source.range("Callee")))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
EXPECT_THAT(
IncomingLevel2,
ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
@@ -256,7 +256,7 @@ TEST(CallHierarchy, IncomingQualified) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
- auto Incoming = incomingCalls(Items[0], Index.get());
+ auto Incoming = incomingCalls(Items[0], Index.get(), AST);
EXPECT_THAT(
Incoming,
ElementsAre(
@@ -396,13 +396,13 @@ TEST(CallHierarchy, MultiFileCpp) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Pos, TUPath);
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(AllOf(withName("caller1"),
withDetail("nsa::caller1"))),
iFromRanges(Caller1C.range()))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(
IncomingLevel2,
ElementsAre(
@@ -411,13 +411,13 @@ TEST(CallHierarchy, MultiFileCpp) {
AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),
iFromRanges(Caller3C.range("Caller1")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+ auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel3,
ElementsAre(AllOf(from(AllOf(withName("caller3"),
withDetail("nsa::caller3"))),
iFromRanges(Caller3C.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+ auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
};
@@ -553,12 +553,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Pos, TUPath);
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(withName("caller1")),
iFromRanges(Caller1C.range()))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+ auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel2,
ElementsAre(AllOf(from(withName("caller2")),
iFromRanges(Caller2C.range("A"),
@@ -566,12 +566,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
AllOf(from(withName("caller3")),
iFromRanges(Caller3C.range("Caller1")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+ auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel3,
ElementsAre(AllOf(from(withName("caller3")),
iFromRanges(Caller3C.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+ auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
};
@@ -616,7 +616,7 @@ TEST(CallHierarchy, CallInLocalVarDecl) {
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto Incoming = incomingCalls(Items[0], Index.get());
+ auto Incoming = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),
iFromRanges(Source.range("call1"))),
AllOf(from(withName("caller2")),
@@ -643,7 +643,7 @@ TEST(CallHierarchy, HierarchyOnField) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("var1")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(withName("caller")),
iFromRanges(Source.range("Callee")))));
@@ -664,7 +664,7 @@ TEST(CallHierarchy, HierarchyOnVar) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("var")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(withName("caller")),
iFromRanges(Source.range("Callee")))));
@@ -686,14 +686,14 @@ TEST(CallHierarchy, HierarchyOnEnumConstant) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("heads")));
- auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(withName("caller")),
iFromRanges(Source.range("CallerH")))));
Items =
prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("tails")));
- IncomingLevel1 = incomingCalls(Items[0], Index.get());
+ IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(IncomingLevel1,
ElementsAre(AllOf(from(withName("caller")),
iFromRanges(Source.range("CallerT")))));
@@ -718,7 +718,7 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) {
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));
- auto Incoming = incomingCalls(Items[0], Index.get());
+ auto Incoming = incomingCalls(Items[0], Index.get(), AST);
// The only call site is in the source file, which is a different file from
// the declaration of the function containing the call, which is in the
>From 0a852bb10ddbf56a6e42cc62aea2818b8c93e973 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Sun, 14 Dec 2025 19:49:49 +0100
Subject: [PATCH 06/15] Collect symbols tags from AST in the method
outgoingCalls.
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 2 +-
clang-tools-extra/clangd/ClangdServer.cpp | 12 ++++--
clang-tools-extra/clangd/ClangdServer.h | 2 +-
clang-tools-extra/clangd/XRefs.cpp | 42 ++++++++++---------
clang-tools-extra/clangd/XRefs.h | 3 +-
.../clangd/unittests/CallHierarchyTests.cpp | 20 ++++-----
6 files changed, 45 insertions(+), 36 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 3fa217ff833bb..0eaab92e5109f 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1434,7 +1434,7 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
void ClangdLSPServer::onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &Params,
Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
- Server->outgoingCalls(Params.item, std::move(Reply));
+ Server->outgoingCalls(Params.item.uri.file(), Params.item, std::move(Reply));
}
void ClangdLSPServer::applyConfiguration(
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 21d69b99ad8d4..cb26c7a799f5e 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -933,12 +933,16 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
}
void ClangdServer::outgoingCalls(
+ PathRef File,
const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
- WorkScheduler->run("Outgoing Calls", "",
- [CB = std::move(CB), Item, this]() mutable {
- CB(clangd::outgoingCalls(Item, Index));
- });
+ auto Action = [Item, CB = std::move(CB), this](
+ llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::outgoingCalls(Item, Index, InpAST->AST));
+ };
+ WorkScheduler->runWithAST("Outgoing Calls", File, std::move(Action));
}
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index ae7e25e2b6db6..c28224e8bbb6b 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -304,7 +304,7 @@ class ClangdServer {
Callback<std::vector<CallHierarchyIncomingCall>> CB);
/// Resolve outgoing calls for a given call hierarchy item.
- void outgoingCalls(const CallHierarchyItem &Item,
+ void outgoingCalls(PathRef File, const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>>);
/// Resolve inlay hints for a given document.
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index f326c3ecea92d..6ef7626cec6ff 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -2445,27 +2445,21 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
auto It = CallsIn.find(Caller.ID);
assert(It != CallsIn.end());
+
+ std::optional<CallHierarchyItem> CHI;
if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) {
- if (auto CHI = declToCallHierarchyItem(*ND, AST.tuPath())) {
- std::vector<Range> FromRanges;
- for (const Location &L : It->second) {
- if (L.uri != CHI->uri) {
- // Call location not in same file as caller.
- // This can happen in some edge cases. There's not much we can do,
- // since the protocol only allows returning ranges interpreted as
- // being in the caller's file.
- continue;
- }
- FromRanges.push_back(L.range);
- }
- Results.push_back(CallHierarchyIncomingCall{
- std::move(*CHI), std::move(FromRanges), MightNeverCall});
- }
- } else if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
- // Fallback to using symbol if NamedDecl is not available
+ CHI = declToCallHierarchyItem(*ND, AST.tuPath());
+ } else {
+ CHI = symbolToCallHierarchyItem(Caller, Item.uri.file());
+ }
+ if (CHI) {
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != CHI->uri) {
+ // Call location not in same file as caller.
+ // This can happen in some edge cases. There's not much we can do,
+ // since the protocol only allows returning ranges interpreted as
+ // being in the caller's file.
continue;
}
FromRanges.push_back(L.range);
@@ -2495,7 +2489,8 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
}
std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
+outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST) {
std::vector<CallHierarchyOutgoingCall> Results;
if (!Index || Item.data.empty())
return Results;
@@ -2541,7 +2536,16 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
auto It = CallsOut.find(Callee.ID);
assert(It != CallsOut.end());
- if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file())) {
+
+ std::optional<CallHierarchyItem> CHI;
+
+ if (auto *ND = getNamedDeclFromSymbol(Callee, AST)) {
+ CHI = declToCallHierarchyItem(*ND, AST.tuPath());
+ } else {
+ CHI = symbolToCallHierarchyItem(Callee, Item.uri.file());
+ }
+
+ if (CHI) {
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != Item.uri) {
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 1019fa189a613..6319729ba39e4 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -152,7 +152,8 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
const ParsedAST &AST);
std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
+outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST);
/// Returns all decls that are referenced in the \p FD except local symbols.
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index f5e9983aa70ec..2891b420427d2 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -296,29 +296,29 @@ TEST(CallHierarchy, OutgoingOneFile) {
std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("caller3")));
- auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());
+ auto OugoingLevel1 = outgoingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(
OugoingLevel1,
ElementsAre(
- AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
+ AllOf(to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))),
oFromRanges(Source.range("Caller1C"))),
AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
oFromRanges(Source.range("Caller2")))));
- auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());
+ auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get(), AST);
ASSERT_THAT(
OutgoingLevel2,
ElementsAre(AllOf(
- to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
+ to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))),
oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
- auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
+ auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST);
ASSERT_THAT(
OutgoingLevel3,
ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
oFromRanges(Source.range("Callee")))));
- auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
+ auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST);
EXPECT_THAT(OutgoingLevel4, IsEmpty());
}
@@ -430,7 +430,7 @@ TEST(CallHierarchy, MultiFileCpp) {
ElementsAre(AllOf(
withName("caller3"),
withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
- auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
+ auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(
OutgoingLevel1,
// fromRanges are interpreted in the context of Items[0]'s file.
@@ -444,19 +444,19 @@ TEST(CallHierarchy, MultiFileCpp) {
IsDeclaration ? oFromRanges()
: oFromRanges(Caller3C.range("Caller2")))));
- auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
+ auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get(), AST);
ASSERT_THAT(OutgoingLevel2,
ElementsAre(AllOf(
to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
- auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
+ auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST);
ASSERT_THAT(
OutgoingLevel3,
ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
oFromRanges(Caller1C.range()))));
- auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
+ auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST);
EXPECT_THAT(OutgoingLevel4, IsEmpty());
};
>From 57fb1ac6e488a43e575d1c21e894d9985053da0a Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 15 Dec 2025 11:02:01 +0100
Subject: [PATCH 07/15] Collect symbols tags from AST in the method subtypes.
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 4 +-
clang-tools-extra/clangd/ClangdServer.cpp | 36 +++++---
clang-tools-extra/clangd/ClangdServer.h | 4 +-
clang-tools-extra/clangd/XRefs.cpp | 89 +++++++++++--------
clang-tools-extra/clangd/XRefs.h | 5 +-
.../clangd/test/type-hierarchy-ext.test | 8 +-
.../clangd/test/type-hierarchy.test | 4 +-
.../clangd/unittests/TypeHierarchyTests.cpp | 4 +-
8 files changed, 91 insertions(+), 63 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 0eaab92e5109f..e77093f31eda6 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1356,7 +1356,7 @@ void ClangdLSPServer::onResolveTypeHierarchy(
}
Reply(serializeTHIForExtension(std::move(**Resp)));
};
- Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction,
+ Server->resolveTypeHierarchy(Params.item.uri.file(), Params.item, Params.resolve, Params.direction,
std::move(Serialize));
}
@@ -1376,7 +1376,7 @@ void ClangdLSPServer::onSuperTypes(
void ClangdLSPServer::onSubTypes(
const ResolveTypeHierarchyItemParams &Params,
Callback<std::vector<TypeHierarchyItem>> Reply) {
- Server->subTypes(Params.item, std::move(Reply));
+ Server->subTypes(Params.item.uri.file(), Params.item, std::move(Reply));
}
void ClangdLSPServer::onPrepareCallHierarchy(
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index cb26c7a799f5e..c210eda6bfd2e 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -880,21 +880,37 @@ void ClangdServer::superTypes(
});
}
-void ClangdServer::subTypes(const TypeHierarchyItem &Item,
+void ClangdServer::subTypes(PathRef File, const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB) {
- WorkScheduler->run(
- "typeHierarchy/subTypes", /*Path=*/"",
- [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
+
+ auto Action = [File = File.str(), Item, CB = std::move(CB), this](
+ llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::subTypes(Item, Index, InpAST->AST));
+ };
+ WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
+ // WorkScheduler->run(
+ // "typeHierarchy/subTypes", /*Path=*/"",
+ // [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
}
-void ClangdServer::resolveTypeHierarchy(
+void ClangdServer::resolveTypeHierarchy(PathRef File,
TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
Callback<std::optional<TypeHierarchyItem>> CB) {
- WorkScheduler->run(
- "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
- clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
- CB(Item);
- });
+ auto Action = [=, CB = std::move(CB), this](
+ llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index, InpAST->AST);
+ CB(Item);
+ };
+ WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File, std::move(Action));
+ // WorkScheduler->run(
+ // "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
+ // clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
+ // CB(Item);
+ // });
}
void ClangdServer::prepareCallHierarchy(
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index c28224e8bbb6b..431856aa1fadb 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -286,11 +286,11 @@ class ClangdServer {
void superTypes(const TypeHierarchyItem &Item,
Callback<std::optional<std::vector<TypeHierarchyItem>>> CB);
/// Get direct children of a type hierarchy item.
- void subTypes(const TypeHierarchyItem &Item,
+ void subTypes(PathRef File, const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB);
/// Resolve type hierarchy item in the given direction.
- void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,
+ void resolveTypeHierarchy(PathRef File, TypeHierarchyItem Item, int Resolve,
TypeHierarchyDirection Direction,
Callback<std::optional<TypeHierarchyItem>> CB);
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 6ef7626cec6ff..b48a834224cd7 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1893,18 +1893,58 @@ symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) {
return Result;
}
+// Tries to find a NamedDecl in the AST that matches the given Symbol.
+// Returns nullptr if the symbol is not found in the current AST.
+const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
+ const ParsedAST &AST) {
+ // Try to convert the symbol to a location and find the decl at that location
+ auto SymLoc = symbolToLocation(Sym, AST.tuPath());
+ if (!SymLoc)
+ return nullptr;
+
+ // Check if the symbol location is in the main file
+ if (SymLoc->uri.file() != AST.tuPath())
+ return nullptr;
+
+ // Convert LSP position to source location
+ const auto &SM = AST.getSourceManager();
+ auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
+ if (!CurLoc) {
+ llvm::consumeError(CurLoc.takeError());
+ return nullptr;
+ }
+
+ // Get all decls at this location
+ auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
+ if (Decls.empty())
+ return nullptr;
+
+ // Return the first decl (usually the most specific one)
+ return Decls[0];
+}
+
static void fillSubTypes(const SymbolID &ID,
std::vector<TypeHierarchyItem> &SubTypes,
- const SymbolIndex *Index, int Levels, PathRef TUPath) {
+ const SymbolIndex *Index, int Levels, PathRef TUPath,
+ const ParsedAST &AST) {
RelationsRequest Req;
Req.Subjects.insert(ID);
Req.Predicate = RelationKind::BaseOf;
Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
- if (std::optional<TypeHierarchyItem> ChildSym =
- symbolToTypeHierarchyItem(Object, TUPath)) {
+ std::optional<TypeHierarchyItem> ChildSym;
+
+ if (auto *ND = getNamedDeclFromSymbol(Object, AST)) {
+ ChildSym = declToTypeHierarchyItem(*ND, AST.tuPath());
+ elog("fillSubTypes: declToTypeHierarchyItem, {0}", ChildSym.has_value());
+ } else {
+ ChildSym = symbolToTypeHierarchyItem(Object, TUPath);
+ elog("fillSubTypes: symbolToTypeHierarchyItem, {0}", ChildSym.has_value());
+ }
+ if (ChildSym) {
if (Levels > 1) {
ChildSym->children.emplace();
- fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath);
+ fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath,
+ AST);
}
SubTypes.emplace_back(std::move(*ChildSym));
}
@@ -2287,7 +2327,7 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
if (Index) {
if (auto ID = getSymbolID(CXXRD))
- fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath);
+ fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath, AST);
}
}
Results.emplace_back(std::move(*Result));
@@ -2319,9 +2359,9 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
}
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
- const SymbolIndex *Index) {
+ const SymbolIndex *Index, const ParsedAST &AST) {
std::vector<TypeHierarchyItem> Results;
- fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file());
+ fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST);
for (auto &ChildSym : Results)
ChildSym.data.parents = {Item.data};
return Results;
@@ -2329,7 +2369,8 @@ std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
TypeHierarchyDirection Direction,
- const SymbolIndex *Index) {
+ const SymbolIndex *Index,
+ const ParsedAST &AST) {
// We only support typeHierarchy/resolve for children, because for parents
// we ignore ResolveLevels and return all levels of parents eagerly.
if (!Index || Direction == TypeHierarchyDirection::Parents ||
@@ -2338,37 +2379,7 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
Item.children.emplace();
fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels,
- Item.uri.file());
-}
-
-// Tries to find a NamedDecl in the AST that matches the given Symbol.
-// Returns nullptr if the symbol is not found in the current AST.
-const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
- const ParsedAST &AST) {
- // Try to convert the symbol to a location and find the decl at that location
- auto SymLoc = symbolToLocation(Sym, AST.tuPath());
- if (!SymLoc)
- return nullptr;
-
- // Check if the symbol location is in the main file
- if (SymLoc->uri.file() != AST.tuPath())
- return nullptr;
-
- // Convert LSP position to source location
- const auto &SM = AST.getSourceManager();
- auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
- if (!CurLoc) {
- llvm::consumeError(CurLoc.takeError());
- return nullptr;
- }
-
- // Get all decls at this location
- auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
- if (Decls.empty())
- return nullptr;
-
- // Return the first decl (usually the most specific one)
- return Decls[0];
+ Item.uri.file(), AST);
}
std::vector<CallHierarchyItem>
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 6319729ba39e4..1582c481ea5c2 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -137,11 +137,12 @@ std::optional<std::vector<TypeHierarchyItem>>
superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
/// Returns direct children of a TypeHierarchyItem.
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
- const SymbolIndex *Index);
+ const SymbolIndex *Index,
+ const ParsedAST &AST);
void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
TypeHierarchyDirection Direction,
- const SymbolIndex *Index);
+ const SymbolIndex *Index, const ParsedAST &AST);
/// Get call hierarchy information at \p Pos.
std::vector<CallHierarchyItem>
diff --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
index 983c7538088bf..d635b199d002c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
@@ -17,11 +17,11 @@
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
@@ -162,11 +162,11 @@
# CHECK-NEXT: "name": "Child4",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: }
# CHECK-NEXT: },
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index d1dda4b92c29c..065f129e05d2f 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -125,11 +125,11 @@
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index 406a842f5a008..ae2f806d0b17e 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -756,7 +756,7 @@ struct Child2b : Child1 {};
parentsNotResolved(), childrenNotResolved()))));
resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1,
- TypeHierarchyDirection::Children, Index.get());
+ TypeHierarchyDirection::Children, Index.get(), AST);
EXPECT_THAT(
(*Result.front().children)[0],
@@ -783,7 +783,7 @@ struct Child : Parent1, Parent2 {};
TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_THAT(Result, SizeIs(1));
- auto Children = subTypes(Result.front(), Index.get());
+ auto Children = subTypes(Result.front(), Index.get(), AST);
// Make sure parents are populated when getting children.
// FIXME: This is partial.
>From d74fdd230eae085d6339d8949f7efd9bfa6cf509 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 15 Dec 2025 16:25:26 +0100
Subject: [PATCH 08/15] Collect symbols tags from AST in the method supertypes.
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 61 ++++++++---------
clang-tools-extra/clangd/ClangdLSPServer.h | 11 ++--
clang-tools-extra/clangd/ClangdServer.cpp | 65 +++++++++----------
clang-tools-extra/clangd/ClangdServer.h | 5 +-
clang-tools-extra/clangd/Protocol.h | 3 +
clang-tools-extra/clangd/XRefs.cpp | 48 ++++++++------
clang-tools-extra/clangd/XRefs.h | 6 +-
.../clangd/test/call-hierarchy.test | 3 +
.../clangd/test/type-hierarchy.test | 7 +-
.../clangd/unittests/TypeHierarchyTests.cpp | 2 +-
10 files changed, 109 insertions(+), 102 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index e77093f31eda6..ef65d22d16c3d 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -136,10 +136,9 @@ CodeAction toCodeAction(const Fix &F, const URIForFile &File,
Edit.textDocument = VersionedTextDocumentIdentifier{{File}, Version};
for (const auto &E : F.Edits)
Edit.edits.push_back(
- {E.range, E.newText,
- SupportChangeAnnotation ? E.annotationId : ""});
+ {E.range, E.newText, SupportChangeAnnotation ? E.annotationId : ""});
if (SupportChangeAnnotation) {
- for (const auto &[AID, Annotation]: F.Annotations)
+ for (const auto &[AID, Annotation] : F.Annotations)
Action.edit->changeAnnotations[AID] = Annotation;
}
}
@@ -909,24 +908,24 @@ void ClangdLSPServer::onRename(const RenameParams &Params,
if (!Server->getDraft(File))
return Reply(llvm::make_error<LSPError>(
"onRename called for non-added file", ErrorCode::InvalidParams));
- Server->rename(File, Params.position, Params.newName, Opts.Rename,
- [File, Params, Reply = std::move(Reply),
- this](llvm::Expected<RenameResult> R) mutable {
- if (!R)
- return Reply(R.takeError());
- if (auto Err = validateEdits(*Server, R->GlobalChanges))
- return Reply(std::move(Err));
- WorkspaceEdit Result;
- // FIXME: use documentChanges if SupportDocumentChanges is
- // true.
- Result.changes.emplace();
- for (const auto &Rep : R->GlobalChanges) {
- (*Result
- .changes)[URI::createFile(Rep.first()).toString()] =
- Rep.second.asTextEdits();
- }
- Reply(Result);
- });
+ Server->rename(
+ File, Params.position, Params.newName, Opts.Rename,
+ [File, Params, Reply = std::move(Reply),
+ this](llvm::Expected<RenameResult> R) mutable {
+ if (!R)
+ return Reply(R.takeError());
+ if (auto Err = validateEdits(*Server, R->GlobalChanges))
+ return Reply(std::move(Err));
+ WorkspaceEdit Result;
+ // FIXME: use documentChanges if SupportDocumentChanges is
+ // true.
+ Result.changes.emplace();
+ for (const auto &Rep : R->GlobalChanges) {
+ (*Result.changes)[URI::createFile(Rep.first()).toString()] =
+ Rep.second.asTextEdits();
+ }
+ Reply(Result);
+ });
}
void ClangdLSPServer::onDocumentDidClose(
@@ -1070,7 +1069,7 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
std::map<ClangdServer::DiagRef, clangd::Diagnostic> ToLSPDiags;
ClangdServer::CodeActionInputs Inputs;
- for (const auto& LSPDiag : Params.context.diagnostics) {
+ for (const auto &LSPDiag : Params.context.diagnostics) {
if (auto DiagRef = getDiagRef(File.file(), LSPDiag)) {
ToLSPDiags[*DiagRef] = LSPDiag;
Inputs.Diagnostics.push_back(*DiagRef);
@@ -1079,13 +1078,9 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
Inputs.File = File.file();
Inputs.Selection = Params.range;
Inputs.RequestedActionKinds = Params.context.only;
- Inputs.TweakFilter = [this](const Tweak &T) {
- return Opts.TweakFilter(T);
- };
- auto CB = [this,
- Reply = std::move(Reply),
- ToLSPDiags = std::move(ToLSPDiags), File,
- Selection = Params.range](
+ Inputs.TweakFilter = [this](const Tweak &T) { return Opts.TweakFilter(T); };
+ auto CB = [this, Reply = std::move(Reply), ToLSPDiags = std::move(ToLSPDiags),
+ File, Selection = Params.range](
llvm::Expected<ClangdServer::CodeActionResult> Fixits) mutable {
if (!Fixits)
return Reply(Fixits.takeError());
@@ -1094,8 +1089,7 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
for (const auto &QF : Fixits->QuickFixes) {
CAs.push_back(toCodeAction(QF.F, File, Version, SupportsDocumentChanges,
SupportsChangeAnnotation));
- if (auto It = ToLSPDiags.find(QF.Diag);
- It != ToLSPDiags.end()) {
+ if (auto It = ToLSPDiags.find(QF.Diag); It != ToLSPDiags.end()) {
CAs.back().diagnostics = {It->second};
}
}
@@ -1356,7 +1350,8 @@ void ClangdLSPServer::onResolveTypeHierarchy(
}
Reply(serializeTHIForExtension(std::move(**Resp)));
};
- Server->resolveTypeHierarchy(Params.item.uri.file(), Params.item, Params.resolve, Params.direction,
+ Server->resolveTypeHierarchy(Params.item.uri.file(), Params.item,
+ Params.resolve, Params.direction,
std::move(Serialize));
}
@@ -1370,7 +1365,7 @@ void ClangdLSPServer::onPrepareTypeHierarchy(
void ClangdLSPServer::onSuperTypes(
const ResolveTypeHierarchyItemParams &Params,
Callback<std::optional<std::vector<TypeHierarchyItem>>> Reply) {
- Server->superTypes(Params.item, std::move(Reply));
+ Server->superTypes(Params.item.uri.file(), Params.item, std::move(Reply));
}
void ClangdLSPServer::onSubTypes(
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6ada3fd9e6e47..64dcbfbc55325 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -133,7 +133,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
Callback<std::vector<Location>>);
void onGoToImplementation(const TextDocumentPositionParams &,
Callback<std::vector<Location>>);
- void onReference(const ReferenceParams &, Callback<std::vector<ReferenceLocation>>);
+ void onReference(const ReferenceParams &,
+ Callback<std::vector<ReferenceLocation>>);
void onSwitchSourceHeader(const TextDocumentIdentifier &,
Callback<std::optional<URIForFile>>);
void onDocumentHighlight(const TextDocumentPositionParams &,
@@ -243,7 +244,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
/// Used to indicate the ClangdLSPServer is being destroyed.
std::atomic<bool> IsBeingDestroyed = {false};
- // FIXME: The caching is a temporary solution to get corresponding clangd
+ // FIXME: The caching is a temporary solution to get corresponding clangd
// diagnostic from a LSP diagnostic.
// Ideally, ClangdServer can generate an identifier for each diagnostic,
// emit them via the LSP's data field (which was newly added in LSP 3.16).
@@ -259,11 +260,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
return {LSPDiag.range, LSPDiag.message};
}
/// A map from LSP diagnostic to clangd-naive diagnostic.
- typedef std::map<DiagKey, ClangdServer::DiagRef>
- DiagnosticToDiagRefMap;
+ typedef std::map<DiagKey, ClangdServer::DiagRef> DiagnosticToDiagRefMap;
/// Caches the mapping LSP and clangd-naive diagnostics per file.
- llvm::StringMap<DiagnosticToDiagRefMap>
- DiagRefMap;
+ llvm::StringMap<DiagnosticToDiagRefMap> DiagRefMap;
// Last semantic-tokens response, for incremental requests.
std::mutex SemanticTokensMutex;
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index c210eda6bfd2e..5b6525d8f842e 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -62,8 +62,8 @@ namespace clangd {
namespace {
// Tracks number of times a tweak has been offered.
-static constexpr trace::Metric TweakAvailable(
- "tweak_available", trace::Metric::Counter, "tweak_id");
+static constexpr trace::Metric
+ TweakAvailable("tweak_available", trace::Metric::Counter, "tweak_id");
// Update the FileIndex with new ASTs and plumb the diagnostics responses.
struct UpdateIndexCallbacks : public ParsingCallbacks {
@@ -872,45 +872,42 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
}
void ClangdServer::superTypes(
- const TypeHierarchyItem &Item,
+ PathRef File, const TypeHierarchyItem &Item,
Callback<std::optional<std::vector<TypeHierarchyItem>>> CB) {
- WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",
- [=, CB = std::move(CB)]() mutable {
- CB(clangd::superTypes(Item, Index));
- });
+ auto Action = [File = File.str(), Item, CB = std::move(CB),
+ this](llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::superTypes(Item, Index, InpAST->AST));
+ };
+ WorkScheduler->runWithAST("superTypes Calls", File, std::move(Action));
}
void ClangdServer::subTypes(PathRef File, const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB) {
- auto Action = [File = File.str(), Item, CB = std::move(CB), this](
- llvm::Expected<InputsAndAST> InpAST) mutable {
- if (!InpAST)
- return CB(InpAST.takeError());
- CB(clangd::subTypes(Item, Index, InpAST->AST));
- };
- WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
- // WorkScheduler->run(
- // "typeHierarchy/subTypes", /*Path=*/"",
- // [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
+ auto Action = [File = File.str(), Item, CB = std::move(CB),
+ this](llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::subTypes(Item, Index, InpAST->AST));
+ };
+ WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
}
-void ClangdServer::resolveTypeHierarchy(PathRef File,
- TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
+void ClangdServer::resolveTypeHierarchy(
+ PathRef File, TypeHierarchyItem Item, int Resolve,
+ TypeHierarchyDirection Direction,
Callback<std::optional<TypeHierarchyItem>> CB) {
- auto Action = [=, CB = std::move(CB), this](
- llvm::Expected<InputsAndAST> InpAST) mutable {
+ auto Action = [=, CB = std::move(CB),
+ this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index, InpAST->AST);
CB(Item);
};
- WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File, std::move(Action));
- // WorkScheduler->run(
- // "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
- // clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
- // CB(Item);
- // });
+ WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File,
+ std::move(Action));
}
void ClangdServer::prepareCallHierarchy(
@@ -925,11 +922,10 @@ void ClangdServer::prepareCallHierarchy(
}
void ClangdServer::incomingCalls(
- PathRef File,
- const CallHierarchyItem &Item,
+ PathRef File, const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>> CB) {
- auto Action = [Item, CB = std::move(CB), this](
- llvm::Expected<InputsAndAST> InpAST) mutable {
+ auto Action = [Item, CB = std::move(CB),
+ this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::incomingCalls(Item, Index, InpAST->AST));
@@ -949,11 +945,10 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
}
void ClangdServer::outgoingCalls(
- PathRef File,
- const CallHierarchyItem &Item,
+ PathRef File, const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
- auto Action = [Item, CB = std::move(CB), this](
- llvm::Expected<InputsAndAST> InpAST) mutable {
+ auto Action = [Item, CB = std::move(CB),
+ this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::outgoingCalls(Item, Index, InpAST->AST));
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 431856aa1fadb..c582a96691deb 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -283,7 +283,7 @@ class ClangdServer {
TypeHierarchyDirection Direction,
Callback<std::vector<TypeHierarchyItem>> CB);
/// Get direct parents of a type hierarchy item.
- void superTypes(const TypeHierarchyItem &Item,
+ void superTypes(PathRef File, const TypeHierarchyItem &Item,
Callback<std::optional<std::vector<TypeHierarchyItem>>> CB);
/// Get direct children of a type hierarchy item.
void subTypes(PathRef File, const TypeHierarchyItem &Item,
@@ -299,8 +299,7 @@ class ClangdServer {
Callback<std::vector<CallHierarchyItem>> CB);
/// Resolve incoming calls for a given call hierarchy item.
- void incomingCalls(PathRef File,
- const CallHierarchyItem &Item,
+ void incomingCalls(PathRef File, const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>> CB);
/// Resolve outgoing calls for a given call hierarchy item.
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index a88c9a391f97a..15faec19bc6a4 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1548,6 +1548,9 @@ struct TypeHierarchyItem {
/// The kind of this item.
SymbolKind kind;
+ /// The symbol tags for this item.
+ std::vector<SymbolTag> tags;
+
/// More detail for this item, e.g. the signature of a function.
std::optional<std::string> detail;
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index b48a834224cd7..fc1bc75de26e5 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1812,6 +1812,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
HI.name = printName(Ctx, ND);
HI.detail = printQualifiedName(ND);
HI.kind = SK;
+ HI.tags = getSymbolTags(ND);
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
HI.selectionRange = Range{NameBegin, NameEnd};
@@ -1930,15 +1931,14 @@ static void fillSubTypes(const SymbolID &ID,
RelationsRequest Req;
Req.Subjects.insert(ID);
Req.Predicate = RelationKind::BaseOf;
- Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
+ Index->relations(Req, [&Levels, &Index, &SubTypes, &TUPath,
+ &AST](const SymbolID &Subject, const Symbol &Object) {
std::optional<TypeHierarchyItem> ChildSym;
if (auto *ND = getNamedDeclFromSymbol(Object, AST)) {
ChildSym = declToTypeHierarchyItem(*ND, AST.tuPath());
- elog("fillSubTypes: declToTypeHierarchyItem, {0}", ChildSym.has_value());
} else {
ChildSym = symbolToTypeHierarchyItem(Object, TUPath);
- elog("fillSubTypes: symbolToTypeHierarchyItem, {0}", ChildSym.has_value());
}
if (ChildSym) {
if (Levels > 1) {
@@ -2167,15 +2167,15 @@ static QualType typeForNode(const ASTContext &Ctx, const HeuristicResolver *H,
return QualType();
}
-// Given a type targeted by the cursor, return one or more types that are more interesting
-// to target.
-static void unwrapFindType(
- QualType T, const HeuristicResolver* H, llvm::SmallVector<QualType>& Out) {
+// Given a type targeted by the cursor, return one or more types that are more
+// interesting to target.
+static void unwrapFindType(QualType T, const HeuristicResolver *H,
+ llvm::SmallVector<QualType> &Out) {
if (T.isNull())
return;
// If there's a specific type alias, point at that rather than unwrapping.
- if (const auto* TDT = T->getAs<TypedefType>())
+ if (const auto *TDT = T->getAs<TypedefType>())
return Out.push_back(QualType(TDT, 0));
// Pointers etc => pointee type.
@@ -2209,8 +2209,8 @@ static void unwrapFindType(
}
// Convenience overload, to allow calling this without the out-parameter
-static llvm::SmallVector<QualType> unwrapFindType(
- QualType T, const HeuristicResolver* H) {
+static llvm::SmallVector<QualType> unwrapFindType(QualType T,
+ const HeuristicResolver *H) {
llvm::SmallVector<QualType> Result;
unwrapFindType(T, H, Result);
return Result;
@@ -2232,9 +2232,9 @@ std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos,
std::vector<LocatedSymbol> LocatedSymbols;
// NOTE: unwrapFindType might return duplicates for something like
- // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some
- // information about the type you may have not known before
- // (since unique_ptr<unique_ptr<T>> != unique_ptr<T>).
+ // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you
+ // some information about the type you may have not known before (since
+ // unique_ptr<unique_ptr<T>> != unique_ptr<T>).
for (const QualType &Type : unwrapFindType(
typeForNode(AST.getASTContext(), AST.getHeuristicResolver(), N),
AST.getHeuristicResolver()))
@@ -2327,7 +2327,8 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
if (Index) {
if (auto ID = getSymbolID(CXXRD))
- fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath, AST);
+ fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath,
+ AST);
}
}
Results.emplace_back(std::move(*Result));
@@ -2337,7 +2338,8 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
}
std::optional<std::vector<TypeHierarchyItem>>
-superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST) {
std::vector<TypeHierarchyItem> Results;
if (!Item.data.parents)
return std::nullopt;
@@ -2349,8 +2351,14 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
Req.IDs.insert(Parent.symbolID);
IDToData[Parent.symbolID] = &Parent;
}
- Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) {
- if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) {
+ Index->lookup(Req, [&Item, &Results, &IDToData, &AST](const Symbol &S) {
+ std::optional<TypeHierarchyItem> THI;
+ if (auto *ND = getNamedDeclFromSymbol(S, AST)) {
+ THI = declToTypeHierarchyItem(*ND, AST.tuPath());
+ } else {
+ THI = symbolToTypeHierarchyItem(S, Item.uri.file());
+ }
+ if (THI) {
THI->data = *IDToData.lookup(S.ID);
Results.emplace_back(std::move(*THI));
}
@@ -2359,7 +2367,8 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
}
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
- const SymbolIndex *Index, const ParsedAST &AST) {
+ const SymbolIndex *Index,
+ const ParsedAST &AST) {
std::vector<TypeHierarchyItem> Results;
fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST);
for (auto &ChildSym : Results)
@@ -2369,8 +2378,7 @@ std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
TypeHierarchyDirection Direction,
- const SymbolIndex *Index,
- const ParsedAST &AST) {
+ const SymbolIndex *Index, const ParsedAST &AST) {
// We only support typeHierarchy/resolve for children, because for parents
// we ignore ResolveLevels and return all levels of parents eagerly.
if (!Index || Direction == TypeHierarchyDirection::Parents ||
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 1582c481ea5c2..f2652b5f274db 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -132,9 +132,11 @@ std::vector<TypeHierarchyItem> getTypeHierarchy(
const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
/// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside
-/// the item.
+/// the item or using the AST.
std::optional<std::vector<TypeHierarchyItem>>
-superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index,
+ const ParsedAST &AST);
+
/// Returns direct children of a TypeHierarchyItem.
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
const SymbolIndex *Index,
diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index 44c89d22d9c85..aba1418e3ec84 100644
--- a/clang-tools-extra/clangd/test/call-hierarchy.test
+++ b/clang-tools-extra/clangd/test/call-hierarchy.test
@@ -32,6 +32,9 @@
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
+# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 18
+# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
---
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index 065f129e05d2f..c7b8a16ae51e2 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -46,6 +46,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 18,
# CHECK-NEXT: 19
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
@@ -71,11 +72,11 @@
# CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: }
# CHECK-NEXT: },
@@ -90,6 +91,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 18,
# CHECK-NEXT: 19
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
@@ -144,6 +146,7 @@
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "tags": [
+# CHECK-NEXT: 18,
# CHECK-NEXT: 19
# CHECK-NEXT: ],
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index ae2f806d0b17e..8696e6d4b0790 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -809,7 +809,7 @@ struct Chil^d : Parent {};
TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_THAT(Result, SizeIs(1));
- auto Parents = superTypes(Result.front(), Index.get());
+ auto Parents = superTypes(Result.front(), Index.get(), AST);
EXPECT_THAT(Parents, Optional(UnorderedElementsAre(
AllOf(withName("Parent"),
>From f40d2c70912aab9d166b0e81330973722d614f66 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 22 Jan 2026 13:18:40 +0100
Subject: [PATCH 09/15] Extended unit-tests to check the occurrence of symbol
tags in call-hierarchy and type-hierarchy.
---
.../clangd/unittests/CallHierarchyTests.cpp | 56 +++++++++++++++++++
.../clangd/unittests/TypeHierarchyTests.cpp | 30 ++++++----
2 files changed, 76 insertions(+), 10 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 2891b420427d2..1d2ae22463a08 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -48,6 +48,12 @@ MATCHER_P(withDetail, N, "") { return arg.detail == N; }
MATCHER_P(withFile, N, "") { return arg.uri.file() == N; }
MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
+template <typename... Tags>
+::testing::Matcher<CallHierarchyItem> withSymbolTags(Tags... tags) {
+ // Matches the tags vector ignoring element order.
+ return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...));
+}
+
template <class ItemMatcher>
::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
return Field(&CallHierarchyIncomingCall::from, M);
@@ -728,6 +734,56 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) {
ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
}
+TEST(CallHierarchy, IncomingCalls) {
+ Annotations Source(R"cpp(
+ class A {
+ public:
+ void call^ee() {};
+ };
+ void caller(A &a) {
+ a.callee();
+ }
+ )cpp");
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+ auto Index = TU.index();
+
+ std::vector<CallHierarchyItem> Items =
+ prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+ ASSERT_THAT(Items, ElementsAre(withName("callee")));
+
+ auto Incoming = incomingCalls(Items[0], Index.get(), AST);
+ EXPECT_THAT(
+ Incoming,
+ UnorderedElementsAre(AllOf(from(
+ AllOf(withName("caller"), withSymbolTags(SymbolTag::Declaration,
+ SymbolTag::Definition))))));
+}
+
+TEST(CallHierarchy, OutgoingCalls) {
+ Annotations Source(R"cpp(
+ void callee() {}
+ class A {
+ public:
+ void call^er() {
+ callee();
+ };
+ };
+ )cpp");
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+ auto Index = TU.index();
+
+ std::vector<CallHierarchyItem> Items =
+ prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+ ASSERT_THAT(Items, ElementsAre(withName("caller")));
+
+ auto Outgoing = outgoingCalls(Items[0], Index.get(), AST);
+ EXPECT_THAT(Outgoing, UnorderedElementsAre(AllOf(
+ to(AllOf(withName("callee"),
+ withSymbolTags(SymbolTag::Declaration,
+ SymbolTag::Definition))))));
+}
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index 8696e6d4b0790..21adbb6d0d3a3 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -54,6 +54,12 @@ MATCHER_P(withResolveParents, M, "") {
return testing::ExplainMatchResult(M, arg.data.parents, result_listener);
}
+template <typename... Tags>
+::testing::Matcher<TypeHierarchyItem> withSymbolTags(Tags... tags) {
+ // Matches the tags vector ignoring element order.
+ return Field(&TypeHierarchyItem::tags, UnorderedElementsAre(tags...));
+}
+
TEST(FindRecordTypeAt, TypeOrVariable) {
Annotations Source(R"cpp(
struct Ch^ild2 {
@@ -770,10 +776,10 @@ struct Child2b : Child1 {};
TEST(Standard, SubTypes) {
Annotations Source(R"cpp(
-struct Pare^nt1 {};
-struct Parent2 {};
-struct Child : Parent1, Parent2 {};
-)cpp");
+ struct Pare^nt1 {};
+ struct Parent2 {};
+ struct Child final : Parent1, Parent2 {};
+ )cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
@@ -791,15 +797,17 @@ struct Child : Parent1, Parent2 {};
Children,
UnorderedElementsAre(
AllOf(withName("Child"),
+ withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition,
+ SymbolTag::Final),
withResolveParents(Optional(UnorderedElementsAre(withResolveID(
getSymbolID(&findDecl(AST, "Parent1")).str())))))));
}
TEST(Standard, SuperTypes) {
Annotations Source(R"cpp(
-struct Parent {};
-struct Chil^d : Parent {};
-)cpp");
+ struct Parent {};
+ struct Chil^d : Parent {};
+ )cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
@@ -811,9 +819,11 @@ struct Chil^d : Parent {};
ASSERT_THAT(Result, SizeIs(1));
auto Parents = superTypes(Result.front(), Index.get(), AST);
- EXPECT_THAT(Parents, Optional(UnorderedElementsAre(
- AllOf(withName("Parent"),
- withResolveParents(Optional(IsEmpty()))))));
+ EXPECT_THAT(Parents,
+ Optional(UnorderedElementsAre(AllOf(
+ withName("Parent"),
+ withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition),
+ withResolveParents(Optional(IsEmpty()))))));
}
} // namespace
} // namespace clangd
>From 79c265dff9e7f89225b1210e5868bdebd5d97b69 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 23 Jan 2026 15:46:57 +0100
Subject: [PATCH 10/15] Simplify lambda capture by explicitly moving `Item` and
parameters. Fix code format.
---
clang-tools-extra/clangd/ClangdServer.cpp | 2 +-
clang-tools-extra/clangd/FindSymbols.h | 2 +-
clang-tools-extra/clangd/Protocol.cpp | 6 ++--
.../clangd/unittests/CallHierarchyTests.cpp | 28 +++++++++++--------
4 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 5b6525d8f842e..e304db2914483 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -899,7 +899,7 @@ void ClangdServer::resolveTypeHierarchy(
PathRef File, TypeHierarchyItem Item, int Resolve,
TypeHierarchyDirection Direction,
Callback<std::optional<TypeHierarchyItem>> CB) {
- auto Action = [=, CB = std::move(CB),
+ auto Action = [Item = std::move(Item), Resolve, Direction, CB = std::move(CB),
this](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
return CB(InpAST.takeError());
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 169efa9a84584..d85a03ebe449b 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -14,8 +14,8 @@
#include "Protocol.h"
#include "index/Symbol.h"
-#include "llvm/ADT/StringRef.h"
#include "clang/AST/Decl.h"
+#include "llvm/ADT/StringRef.h"
namespace clang {
class NamedDecl;
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 55c06be0ae21c..e1e3180dc4af8 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -209,7 +209,7 @@ bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R,
O.map("needsConfirmation", R.needsConfirmation) &&
O.mapOptional("description", R.description);
}
-llvm::json::Value toJSON(const ChangeAnnotation & CA) {
+llvm::json::Value toJSON(const ChangeAnnotation &CA) {
llvm::json::Object Result{{"label", CA.label}};
if (CA.needsConfirmation)
Result["needsConfirmation"] = *CA.needsConfirmation;
@@ -859,7 +859,7 @@ llvm::json::Value toJSON(const SymbolInformation &P) {
};
if (P.score)
O["score"] = *P.score;
- if(!P.tags.empty())
+ if (!P.tags.empty())
O["tags"] = P.tags;
return std::move(O);
}
@@ -1439,7 +1439,7 @@ llvm::json::Value toJSON(const TypeHierarchyItem &I) {
if (I.detail)
Result["detail"] = I.detail;
- if(!I.tags.empty())
+ if (!I.tags.empty())
Result["tags"] = I.tags;
return std::move(Result);
}
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 1d2ae22463a08..fb911ba07354a 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -305,11 +305,11 @@ TEST(CallHierarchy, OutgoingOneFile) {
auto OugoingLevel1 = outgoingCalls(Items[0], Index.get(), AST);
ASSERT_THAT(
OugoingLevel1,
- ElementsAre(
- AllOf(to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))),
- oFromRanges(Source.range("Caller1C"))),
- AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
- oFromRanges(Source.range("Caller2")))));
+ ElementsAre(AllOf(to(AllOf(withName("Foo::caller1"),
+ withDetail("ns::Foo::caller1"))),
+ oFromRanges(Source.range("Caller1C"))),
+ AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
+ oFromRanges(Source.range("Caller2")))));
auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get(), AST);
ASSERT_THAT(
@@ -408,7 +408,8 @@ TEST(CallHierarchy, MultiFileCpp) {
withDetail("nsa::caller1"))),
iFromRanges(Caller1C.range()))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
+ auto IncomingLevel2 =
+ incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(
IncomingLevel2,
ElementsAre(
@@ -417,13 +418,15 @@ TEST(CallHierarchy, MultiFileCpp) {
AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),
iFromRanges(Caller3C.range("Caller1")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
+ auto IncomingLevel3 =
+ incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel3,
ElementsAre(AllOf(from(AllOf(withName("caller3"),
withDetail("nsa::caller3"))),
iFromRanges(Caller3C.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
+ auto IncomingLevel4 =
+ incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
};
@@ -564,7 +567,8 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
ElementsAre(AllOf(from(withName("caller1")),
iFromRanges(Caller1C.range()))));
- auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
+ auto IncomingLevel2 =
+ incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel2,
ElementsAre(AllOf(from(withName("caller2")),
iFromRanges(Caller2C.range("A"),
@@ -572,12 +576,14 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
AllOf(from(withName("caller3")),
iFromRanges(Caller3C.range("Caller1")))));
- auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
+ auto IncomingLevel3 =
+ incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
ASSERT_THAT(IncomingLevel3,
ElementsAre(AllOf(from(withName("caller3")),
iFromRanges(Caller3C.range("Caller2")))));
- auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
+ auto IncomingLevel4 =
+ incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
EXPECT_THAT(IncomingLevel4, IsEmpty());
};
>From 103cd61fb63272e06ebd982d9012d2eec3f6feec Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at protonmail.com>
Date: Mon, 16 Feb 2026 12:36:00 +0100
Subject: [PATCH 11/15] New: overrides and implements tags.
---
clang-tools-extra/clangd/Protocol.h | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 15faec19bc6a4..c2a87ebe5a875 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1127,10 +1127,12 @@ enum class SymbolTag {
Declaration = 18,
Definition = 19,
ReadOnly = 20,
+ Overrides = 21,
+ Implements = 22,
// Update as needed
FirstTag = Deprecated,
- LastTag = ReadOnly
+ LastTag = Implements
};
llvm::json::Value toJSON(SymbolTag);
/// Represents programming constructs like variables, classes, interfaces etc.
>From 575efcfffb121bc7d613ecb4cacce35d76139a19 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at protonmail.com>
Date: Wed, 18 Feb 2026 11:33:53 +0100
Subject: [PATCH 12/15] Add: comments to the new symbol tags.
---
clang-tools-extra/clangd/Protocol.h | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index c2a87ebe5a875..b423ce2f683c3 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1115,20 +1115,29 @@ enum class SymbolTag {
Internal = 6,
File = 7,
Static = 8,
- Abstract = 9,
- Final = 10,
+ Abstract = 9, // In context of a class and method - this symbol indicates a
+ // pure virtual method.
+ Final = 10, // In context of a method - this symbol indicates that the method
+ // cannot be overridden in subclasses.
+ // In context of a class - this symbol indicates that the class is
+ // final and thus cannot be extended.
Sealed = 11,
Transient = 12,
Volatile = 13,
Synchronized = 14,
- Virtual = 15,
+ Virtual =
+ 15, // In context of a method - this symbol indicates a virtual
+ // method declared and implemented in same class, and thereby it is
+ // not implementing or overriding a method from any base class.
Nullable = 16,
NonNull = 17,
Declaration = 18,
Definition = 19,
ReadOnly = 20,
- Overrides = 21,
- Implements = 22,
+ Overrides = 21, // In context of a method - this symbol indicates a method
+ // overriding a virtual method, implemented in base class.
+ Implements = 22, // In context of a method - this symbol indicates a method
+ // implementing a pure virtual method from a base class.
// Update as needed
FirstTag = Deprecated,
>From 82885575adc0377313b4c3c01a0268fbc7c860e2 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at protonmail.com>
Date: Wed, 18 Feb 2026 11:39:49 +0100
Subject: [PATCH 13/15] New: tests for the tags Implements and Overrides.
Change: refactored and renamed (DocumentSymbolsTest, SymbolTags)
---
.../clangd/unittests/FindSymbolsTests.cpp | 152 +++++++++++++++---
1 file changed, 130 insertions(+), 22 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index d0dd5d0f8f434..c67302922e512 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -1138,12 +1138,125 @@ TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) {
withName("Core"), withName("coreMethod")));
}
-TEST(DocumentSymbolsTest, SymbolTags) {
+TEST(DocumentSymbolsTest, SymbolTagsMustContainPublicAbstract) {
TestTU TU;
Annotations Main(R"cpp(
- class AbstractClass {
+ class A {
public:
- virtual ~AbstractClass() = default;
+ virtual void f1() = 0;
+ };
+
+ class B : public A {
+ public:
+ virtual void f2() = 0;
+ };
+ )cpp");
+
+ TU.Code = Main.code().str();
+ auto Symbols = getSymbols(TU.build());
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(
+ AllOf(withName("A"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Abstract)))),
+ AllOf(withName("B"),
+ children(AllOf(withName("f2"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Abstract))))));
+}
+
+TEST(DocumentSymbolsTest, SymbolTagsMustContainPublicVirtualAndOverrides) {
+ TestTU TU;
+ Annotations Main(R"cpp(
+ class A {
+ public:
+ virtual void f1() {};
+ };
+
+ class B : public A {
+ public:
+ void f1() override {}
+ };
+
+ class C : public B {
+ public:
+ void f1() override {}
+ };
+ )cpp");
+
+ TU.Code = Main.code().str();
+ auto Symbols = getSymbols(TU.build());
+ EXPECT_THAT(
+ Symbols,
+ UnorderedElementsAre(
+ AllOf(withName("A"),
+ children(
+ AllOf(withName("f1"), withSymbolTags(SymbolTag::Public,
+ SymbolTag::Virtual)))),
+ AllOf(withName("B"),
+ children(AllOf(
+ withName("f1"),
+ withSymbolTags(SymbolTag::Public, SymbolTag::Overrides)))),
+ AllOf(withName("C"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Overrides))))));
+}
+
+TEST(DocumentSymbolsTest,
+ SymbolTagsMustContainPublicAbstractImplementsOverridesAndFinal) {
+ TestTU TU;
+ Annotations Main(R"cpp(
+ class A {
+ public:
+ virtual void f1() = 0;
+ };
+
+ class B : public A {
+ public:
+ void f1() override {}
+ };
+
+ class C : public B {
+ public:
+ void f1() override {}
+ };
+
+ class D : public C {
+ public:
+ void f1() final override {}
+ };
+ )cpp");
+
+ TU.Code = Main.code().str();
+ auto Symbols = getSymbols(TU.build());
+ EXPECT_THAT(Symbols,
+ UnorderedElementsAre(
+ AllOf(withName("A"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Abstract)))),
+ AllOf(withName("B"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Implements)))),
+ AllOf(withName("C"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Overrides)))),
+ AllOf(withName("D"),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public,
+ SymbolTag::Final))))));
+}
+
+TEST(DocumentSymbolsTest, SymbolTagsCompilation) {
+ TestTU TU;
+ Annotations Main(R"cpp(
+ class A {
+ public:
+ virtual ~A() = default;
virtual void f1() = 0;
void f2() const;
protected:
@@ -1152,9 +1265,9 @@ TEST(DocumentSymbolsTest, SymbolTags) {
static void f4(){}
};
- void AbstractClass::f2() const {}
+ void A::f2() const {}
- class ImplClass final: public AbstractClass {
+ class B final: public A {
public:
void f1() final {}
};
@@ -1166,18 +1279,14 @@ TEST(DocumentSymbolsTest, SymbolTags) {
Symbols,
UnorderedElementsAre(
AllOf(
- withName("AbstractClass"),
+ withName("A"),
withSymbolTags(SymbolTag::Abstract, SymbolTag::Declaration,
SymbolTag::Definition),
children(
- AllOf(withName("~AbstractClass"),
- withSymbolTags(SymbolTag::Public, SymbolTag::Virtual,
- SymbolTag::Declaration,
- SymbolTag::Definition)),
+ AllOf(withName("~A"),
+ withSymbolTags(SymbolTag::Public, SymbolTag::Virtual)),
AllOf(withName("f1"),
- withSymbolTags(SymbolTag::Public, SymbolTag::Abstract,
- SymbolTag::Virtual,
- SymbolTag::Declaration)),
+ withSymbolTags(SymbolTag::Public, SymbolTag::Abstract)),
AllOf(withName("f2"), withSymbolTags(SymbolTag::Public,
SymbolTag::Declaration,
SymbolTag::ReadOnly)),
@@ -1188,17 +1297,16 @@ TEST(DocumentSymbolsTest, SymbolTags) {
withSymbolTags(SymbolTag::Private, SymbolTag::Static,
SymbolTag::Declaration,
SymbolTag::Definition)))),
- AllOf(withName("AbstractClass::f2"),
+ AllOf(withName("A::f2"),
withSymbolTags(SymbolTag::Public, SymbolTag::Declaration,
SymbolTag::Definition, SymbolTag::ReadOnly)),
- AllOf(withName("ImplClass"),
- withSymbolTags(SymbolTag::Final, SymbolTag::Declaration,
- SymbolTag::Definition),
- children(AllOf(
- withName("f1"),
- withSymbolTags(SymbolTag::Public, SymbolTag::Final,
- SymbolTag::Virtual, SymbolTag::Declaration,
- SymbolTag::Definition))))));
+ AllOf(
+ withName("B"),
+ withSymbolTags(SymbolTag::Final, SymbolTag::Declaration,
+ SymbolTag::Definition),
+ children(AllOf(withName("f1"),
+ withSymbolTags(SymbolTag::Public, SymbolTag::Final,
+ SymbolTag::Implements))))));
}
} // namespace
>From 76f58abcbc2d1a002b4214e0873f9a28f4290057 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at protonmail.com>
Date: Wed, 18 Feb 2026 12:16:43 +0100
Subject: [PATCH 14/15] New: functions calculating the tags Overrides and
Implements. New: function filtering out redundant tags in the sense of
semantics. Change: apply the new filter function on symbol tags in
getSymbolTags.
---
clang-tools-extra/clangd/FindSymbols.cpp | 115 ++++++++++++++++++++++-
1 file changed, 114 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 7fe91f883c79f..da258bcf3f1cf 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -121,6 +121,16 @@ bool isAbstract(const Decl *D) {
// Indicates whether declaration D is virtual in cases where D is a method.
bool isVirtual(const Decl *D) {
+ // We want to treat a method as virtual if it is declared virtual, even if it
+ // is not implemented in this class, or if it overrides/implements a
+ // base-class method. This is because the "virtual" modifier is still relevant
+ // to the method's behavior and how it should be highlighted, even if it is
+ // not itself a virtual method in the strictest sense. For example, a method
+ // that overrides a virtual method from a base class is still considered
+ // virtual, even if it is not declared as such in the derived class.
+ // Similarly, a method that implements a pure virtual method from a base class
+ // is also considered virtual, even if it is not declared as such in the
+ // derived class.
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isVirtual();
return false;
@@ -162,6 +172,45 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
return (1 << static_cast<unsigned>(ST));
}
+bool isOverrides(const NamedDecl *nd) {
+ if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(nd)) {
+ // A method "overrides" if:
+ // 1. It overrides at least one method
+ // 2. At least one of the overridden methods is virtual (but NOT pure
+ // virtual)
+
+ if (MD->size_overridden_methods() == 0)
+ return false;
+
+ for (auto Overridden : MD->overridden_methods()) {
+ // Check if the overridden method is virtual but not pure virtual
+ if (Overridden->isVirtual() && !Overridden->isPureVirtual())
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+bool isImplements(const NamedDecl *nd) {
+ if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(nd)) {
+ // A method "implements" pure virtual methods from base classes if:
+ // 1. It overrides at least one method
+ // 2. It is NOT itself pure virtual (i.e., it has a concrete implementation)
+ // 3. ALL overridden methods are pure virtual
+
+ if (MD->size_overridden_methods() == 0 || MD->isPureVirtual())
+ return false;
+
+ for (auto Overridden : MD->overridden_methods()) {
+ if (!Overridden->isPureVirtual())
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
SymbolTags computeSymbolTags(const NamedDecl &ND) {
SymbolTags Result = 0;
const auto IsDef = isUniqueDefinition(&ND);
@@ -184,6 +233,12 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
if (isFinal(&ND))
Result |= toSymbolTagBitmask(SymbolTag::Final);
+ if (isOverrides(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Overrides);
+
+ if (isImplements(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Implements);
+
if (not isa<UnresolvedUsingValueDecl>(ND)) {
// Do not treat an UnresolvedUsingValueDecl as a declaration.
// It's more common to think of it as a reference to the
@@ -211,6 +266,61 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
return Result;
}
+// Filter symbol tags based on the presence of other tags and the kind of
+// symbol. This is needed to avoid redundant tags.
+SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) {
+ SymbolTags Result = ST;
+ if (isa<CXXMethodDecl>(ND) &&
+ (ST & toSymbolTagBitmask(SymbolTag::Overrides))) {
+ // Overrides means that ND overrides an existing implementation of a virtual
+ // method in a base class. If a symbol is marked as Overrides, the tags
+ // Virtual, Declaration and Definition should be removed, as the Overrides
+ // tag implies that the symbol has/is virtual/declaration/definition.
+ Result &= ~toSymbolTagBitmask(SymbolTag::Virtual);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Declaration);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Definition);
+ }
+ if (isa<CXXMethodDecl>(ND) &&
+ (ST & toSymbolTagBitmask(SymbolTag::Implements))) {
+ // Implements means that ND implements an existing pure virtual method in a
+ // base class. If a symbol is marked as Implements, the tags Virtual,
+ // Declaration, Definition and Overrides should be removed, as the
+ // Implements tag implies that the symbol is virtual, is a declaration, is a
+ // definition, and overrides a method.
+ Result &= ~toSymbolTagBitmask(SymbolTag::Virtual);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Declaration);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Definition);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Overrides);
+ }
+ if (isa<CXXMethodDecl>(ND) && (ST & toSymbolTagBitmask(SymbolTag::Virtual))) {
+ // Virtual means that ND is a virtual method that does not override any
+ // method in a base class. If a symbol is marked as Virtual, the tags
+ // Declaration and Definition should be removed, as the Virtual tag implies
+ // that the symbol is a declaration/definition.
+ Result &= ~toSymbolTagBitmask(SymbolTag::Declaration);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Definition);
+ }
+ if (isa<CXXMethodDecl>(ND) &&
+ (ST & toSymbolTagBitmask(SymbolTag::Abstract))) {
+ // Abstract means that ND is a pure virtual method. If a symbol is marked as
+ // Abstract, the tags Virtual, Declaration and Definition should be removed,
+ // as the Abstract tag implies that the symbol is virtual and a
+ // declaration/definition.
+ Result &= ~toSymbolTagBitmask(SymbolTag::Virtual);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Declaration);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Definition);
+ }
+ if (isa<CXXMethodDecl>(ND) && (ST & toSymbolTagBitmask(SymbolTag::Final))) {
+ // Final means that ND is a method that cannot be overridden by any method
+ // in a derived class. If a symbol is marked as Final, the tags Virtual and
+ // Overrides should be removed, as the Final tag implies that the symbol is
+ // virtual.
+ Result &= ~toSymbolTagBitmask(SymbolTag::Virtual);
+ Result &= ~toSymbolTagBitmask(SymbolTag::Overrides);
+ }
+ return Result;
+}
+
std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
const auto symbolTags = computeSymbolTags(ND);
std::vector<SymbolTag> Tags;
@@ -218,6 +328,9 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
if (symbolTags == 0)
return Tags;
+ // Apply specific filter to the symbol tags.
+ const auto filteredTags = filterSymbolTags(ND, symbolTags);
+
// Iterate through SymbolTag enum values and collect any that are present in
// the bitmask. SymbolTag values are in the numeric range
// [FirstTag .. LastTag].
@@ -225,7 +338,7 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::LastTag);
for (unsigned I = MinTag; I <= MaxTag; ++I) {
auto ST = static_cast<SymbolTag>(I);
- if (symbolTags & toSymbolTagBitmask(ST))
+ if (filteredTags & toSymbolTagBitmask(ST))
Tags.push_back(ST);
}
return Tags;
>From 691f0f333adc46b000911ac374732e4835f67ec3 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at protonmail.com>
Date: Wed, 18 Feb 2026 13:47:29 +0100
Subject: [PATCH 15/15] Fix: removed obsolete tags Virtual (15) and Declaration
(18) cause they are implicitly exposed with the symbol tag Abstract (20).
---
clang-tools-extra/clangd/test/symbol-tags.test | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index 6b17dc994d029..34c06c4b9ba63 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -41,8 +41,6 @@
# CHECK: "tags": [
# CHECK: 2,
# CHECK: 9,
-# CHECK: 15,
-# CHECK: 18,
# CHECK: 20
# CHECK: ]
# CHECK: }
More information about the cfe-commits
mailing list