From 36ca87786fded14d2271cccc732778f249befb63 Mon Sep 17 00:00:00 2001 From: Dimitri Ratz Date: Fri, 28 Nov 2025 14:01:25 +0100 Subject: [PATCH 01/16] 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 getSymbolTags(const NamedDecl &ND) { return Tags; } +std::vector getSymbolTags(const Symbol &S) { + std::vector 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; 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 getSymbolTags(const NamedDecl &ND); +/// Returns the symbol tags for an index `Symbol`. +std::vector 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 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 Date: Mon, 1 Dec 2025 11:34:51 +0100 Subject: [PATCH 02/16] 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::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 Date: Mon, 1 Dec 2025 12:03:35 +0100 Subject: [PATCH 03/16] 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 Date: Wed, 3 Dec 2025 13:09:58 +0100 Subject: [PATCH 04/16] 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 Date: Fri, 12 Dec 2025 12:17:56 +0100 Subject: [PATCH 05/16] 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> 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> 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 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 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> CB); /// Resolve incoming calls for a given call hierarchy item. - void incomingCalls(const CallHierarchyItem &Item, - Callback>); + void incomingCalls(PathRef File, + const CallHierarchyItem &Item, + Callback> 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(AST), *CurLoc, {}); + if (Decls.empty()) + return nullptr; + + // Return the first decl (usually the most specific one) + return Decls[0]; +} + std::vector prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) { std::vector Result; @@ -2368,8 +2398,10 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) { } std::vector -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector 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 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 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 prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath); std::vector -incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST); std::vector 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 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 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 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 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 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 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 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 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 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 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 Date: Sun, 14 Dec 2025 19:49:49 +0100 Subject: [PATCH 06/16] 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> 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 RestrictRange, } void ClangdServer::outgoingCalls( + PathRef File, const CallHierarchyItem &Item, Callback> 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 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> CB); /// Resolve outgoing calls for a given call hierarchy item. - void outgoingCalls(const CallHierarchyItem &Item, + void outgoingCalls(PathRef File, const CallHierarchyItem &Item, Callback>); /// 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 CHI; if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) { - if (auto CHI = declToCallHierarchyItem(*ND, AST.tuPath())) { - std::vector 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 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 -outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { +outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector 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 CHI; + + if (auto *ND = getNamedDeclFromSymbol(Callee, AST)) { + CHI = declToCallHierarchyItem(*ND, AST.tuPath()); + } else { + CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()); + } + + if (CHI) { std::vector 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 -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 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 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 Date: Mon, 15 Dec 2025 11:02:01 +0100 Subject: [PATCH 07/16] 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> 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> 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 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> 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 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>> CB); /// Get direct children of a type hierarchy item. - void subTypes(const TypeHierarchyItem &Item, + void subTypes(PathRef File, const TypeHierarchyItem &Item, Callback> 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> 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(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 &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 ChildSym = - symbolToTypeHierarchyItem(Object, TUPath)) { + std::optional 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 subTypes(const TypeHierarchyItem &Item, - const SymbolIndex *Index) { + const SymbolIndex *Index, const ParsedAST &AST) { std::vector 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 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(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 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> superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index); /// Returns direct children of a TypeHierarchyItem. std::vector 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 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 Date: Mon, 15 Dec 2025 16:25:26 +0100 Subject: [PATCH 08/16] 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( "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 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 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 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 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>> 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>); void onGoToImplementation(const TextDocumentPositionParams &, Callback>); - void onReference(const ReferenceParams &, Callback>); + void onReference(const ReferenceParams &, + Callback>); void onSwitchSourceHeader(const TextDocumentIdentifier &, Callback>); void onDocumentHighlight(const TextDocumentPositionParams &, @@ -243,7 +244,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks, /// Used to indicate the ClangdLSPServer is being destroyed. std::atomic 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 - DiagnosticToDiagRefMap; + typedef std::map DiagnosticToDiagRefMap; /// Caches the mapping LSP and clangd-naive diagnostics per file. - llvm::StringMap - DiagRefMap; + llvm::StringMap 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>> 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 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> CB) { - auto Action = [File = File.str(), Item, CB = std::move(CB), this]( - llvm::Expected 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 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> CB) { - auto Action = [=, CB = std::move(CB), this]( - llvm::Expected InpAST) mutable { + auto Action = [=, CB = std::move(CB), + this](llvm::Expected 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> CB) { - auto Action = [Item, CB = std::move(CB), this]( - llvm::Expected InpAST) mutable { + auto Action = [Item, CB = std::move(CB), + this](llvm::Expected 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 RestrictRange, } void ClangdServer::outgoingCalls( - PathRef File, - const CallHierarchyItem &Item, + PathRef File, const CallHierarchyItem &Item, Callback> CB) { - auto Action = [Item, CB = std::move(CB), this]( - llvm::Expected InpAST) mutable { + auto Action = [Item, CB = std::move(CB), + this](llvm::Expected 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> CB); /// Get direct parents of a type hierarchy item. - void superTypes(const TypeHierarchyItem &Item, + void superTypes(PathRef File, const TypeHierarchyItem &Item, Callback>> CB); /// Get direct children of a type hierarchy item. void subTypes(PathRef File, const TypeHierarchyItem &Item, @@ -299,8 +299,7 @@ class ClangdServer { Callback> 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> 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 tags; + /// More detail for this item, e.g. the signature of a function. std::optional 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 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& 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 &Out) { if (T.isNull()) return; // If there's a specific type alias, point at that rather than unwrapping. - if (const auto* TDT = T->getAs()) + if (const auto *TDT = T->getAs()) 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 unwrapFindType( - QualType T, const HeuristicResolver* H) { +static llvm::SmallVector unwrapFindType(QualType T, + const HeuristicResolver *H) { llvm::SmallVector Result; unwrapFindType(T, H, Result); return Result; @@ -2232,9 +2232,9 @@ std::vector findType(ParsedAST &AST, Position Pos, std::vector LocatedSymbols; // NOTE: unwrapFindType might return duplicates for something like - // unique_ptr>. 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). + // unique_ptr>. 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). 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> -superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { +superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index, + const ParsedAST &AST) { std::vector 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 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 subTypes(const TypeHierarchyItem &Item, - const SymbolIndex *Index, const ParsedAST &AST) { + const SymbolIndex *Index, + const ParsedAST &AST) { std::vector Results; fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST); for (auto &ChildSym : Results) @@ -2369,8 +2378,7 @@ std::vector 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 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> -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 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 Date: Thu, 22 Jan 2026 13:18:40 +0100 Subject: [PATCH 09/16] 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 +::testing::Matcher withSymbolTags(Tags... tags) { + // Matches the tags vector ignoring element order. + return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...)); +} + template ::testing::Matcher 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 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 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 +::testing::Matcher 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 Date: Fri, 23 Jan 2026 15:46:57 +0100 Subject: [PATCH 10/16] 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> CB) { - auto Action = [=, CB = std::move(CB), + auto Action = [Item = std::move(Item), Resolve, Direction, CB = std::move(CB), this](llvm::Expected 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 Date: Mon, 16 Feb 2026 12:36:00 +0100 Subject: [PATCH 11/16] 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 Date: Wed, 18 Feb 2026 11:33:53 +0100 Subject: [PATCH 12/16] 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 Date: Wed, 18 Feb 2026 11:39:49 +0100 Subject: [PATCH 13/16] 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 Date: Wed, 18 Feb 2026 12:16:43 +0100 Subject: [PATCH 14/16] 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(D)) return CMD->isVirtual(); return false; @@ -162,6 +172,45 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) { return (1 << static_cast(ST)); } +bool isOverrides(const NamedDecl *nd) { + if (const auto *MD = llvm::dyn_cast(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(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(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(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(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(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(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(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 getSymbolTags(const NamedDecl &ND) { const auto symbolTags = computeSymbolTags(ND); std::vector Tags; @@ -218,6 +328,9 @@ std::vector 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 getSymbolTags(const NamedDecl &ND) { constexpr unsigned MaxTag = static_cast(SymbolTag::LastTag); for (unsigned I = MinTag; I <= MaxTag; ++I) { auto ST = static_cast(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 Date: Wed, 18 Feb 2026 13:47:29 +0100 Subject: [PATCH 15/16] 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: } From 7465963a388325db65b769895fdd0bbf8c9e4372 Mon Sep 17 00:00:00 2001 From: Dimitri Ratz Date: Mon, 23 Feb 2026 12:48:57 +0100 Subject: [PATCH 16/16] Change: minor improvements. --- clang-tools-extra/clangd/FindSymbols.cpp | 29 ++++++++++++------------ clang-tools-extra/clangd/Protocol.h | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index da258bcf3f1cf..f82003e383edf 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -172,8 +172,8 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) { return (1 << static_cast(ST)); } -bool isOverrides(const NamedDecl *nd) { - if (const auto *MD = llvm::dyn_cast(nd)) { +bool isOverrides(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(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 @@ -182,7 +182,7 @@ bool isOverrides(const NamedDecl *nd) { if (MD->size_overridden_methods() == 0) return false; - for (auto Overridden : MD->overridden_methods()) { + for (const auto Overridden : MD->overridden_methods()) { // Check if the overridden method is virtual but not pure virtual if (Overridden->isVirtual() && !Overridden->isPureVirtual()) return true; @@ -192,8 +192,8 @@ bool isOverrides(const NamedDecl *nd) { return false; } -bool isImplements(const NamedDecl *nd) { - if (const auto *MD = llvm::dyn_cast(nd)) { +bool isImplements(const NamedDecl *ND) { + if (const auto *MD = llvm::dyn_cast(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) @@ -202,7 +202,7 @@ bool isImplements(const NamedDecl *nd) { if (MD->size_overridden_methods() == 0 || MD->isPureVirtual()) return false; - for (auto Overridden : MD->overridden_methods()) { + for (const auto Overridden : MD->overridden_methods()) { if (!Overridden->isPureVirtual()) return false; } @@ -270,8 +270,11 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) { // symbol. This is needed to avoid redundant tags. SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { SymbolTags Result = ST; - if (isa(ND) && - (ST & toSymbolTagBitmask(SymbolTag::Overrides))) { + + if (not isa(ND)) + return Result; + + if (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 @@ -280,8 +283,7 @@ SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); Result &= ~toSymbolTagBitmask(SymbolTag::Definition); } - if (isa(ND) && - (ST & toSymbolTagBitmask(SymbolTag::Implements))) { + if (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 @@ -292,7 +294,7 @@ SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { Result &= ~toSymbolTagBitmask(SymbolTag::Definition); Result &= ~toSymbolTagBitmask(SymbolTag::Overrides); } - if (isa(ND) && (ST & toSymbolTagBitmask(SymbolTag::Virtual))) { + if (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 @@ -300,8 +302,7 @@ SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); Result &= ~toSymbolTagBitmask(SymbolTag::Definition); } - if (isa(ND) && - (ST & toSymbolTagBitmask(SymbolTag::Abstract))) { + if (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 @@ -310,7 +311,7 @@ SymbolTags filterSymbolTags(const NamedDecl &ND, const SymbolTags ST) { Result &= ~toSymbolTagBitmask(SymbolTag::Declaration); Result &= ~toSymbolTagBitmask(SymbolTag::Definition); } - if (isa(ND) && (ST & toSymbolTagBitmask(SymbolTag::Final))) { + if (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 diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index b423ce2f683c3..2450c476f1c55 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1116,7 +1116,7 @@ enum class SymbolTag { File = 7, Static = 8, Abstract = 9, // In context of a class and method - this symbol indicates a - // pure virtual method. + // pure virtual class or 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