Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
910a890
Fill symbol tags for the response TypeHierarchyItem of prepareTypeHie…
Nov 28, 2025
01a022e
Fill symbol tags into the object SymbolInformation, and on getting wo…
Dec 1, 2025
c450d62
Minor improvements.
Dec 1, 2025
c297add
Set the detail field of HierarchyItem.
Dec 3, 2025
35ee021
Collect symbols tags from AST in the method incomingCalls.
Dec 12, 2025
89237d1
Collect symbols tags from AST in the method outgoingCalls.
Dec 14, 2025
9eb72dc
Collect symbols tags from AST in the method subtypes.
Dec 15, 2025
0b1c3f6
Collect symbols tags from AST in the method supertypes.
Dec 15, 2025
7e11d69
Extended unit-tests to check the occurrence of symbol tags
Jan 22, 2026
007764f
Simplify lambda capture by explicitly moving `Item` and parameters.
Jan 23, 2026
f1fdea3
Feat: functions calculating the tags Overrides and Implements.
ratzdi Feb 16, 2026
2c0475f
Feat: compute symbol tags during the AST indexation.
ratzdi Feb 25, 2026
525a6ec
Merge branch 'llvm:main' into users/ratzdi/symbol_tags_in_call_type_h…
ratzdi Mar 6, 2026
d6865cc
Change: removed AST usage in call/type hierarchy
ratzdi Mar 19, 2026
66529e5
Feat: serialize/de-serialize symbol tags.
ratzdi Mar 20, 2026
fad6461
Review: code review adjustments.
ratzdi Mar 23, 2026
f99012b
Change: relocated Tags to decrease padding.
ratzdi Mar 24, 2026
317abd3
Review: code review adjustments.
ratzdi Apr 16, 2026
477dd75
Review: code review adjustments.
ratzdi Apr 21, 2026
765ef3e
Refactor: Make SymbolTags type-safe and adapt tag handling
ratzdi Apr 22, 2026
949f510
Fix: code format.
ratzdi Apr 22, 2026
88aa2a1
Fix: code format.
ratzdi Apr 22, 2026
bdacf73
Replaced toSymbolTagBitmask with SymbolTags::fromTag.
ratzdi Apr 27, 2026
86a41ca
Simplified filterSymbolTags:
ratzdi Apr 27, 2026
2aea848
Revert "Replaced toSymbolTagBitmask with SymbolTags::fromTag."
ratzdi Apr 27, 2026
c39d64b
Revert "Simplified filterSymbolTags:"
ratzdi Apr 27, 2026
d54d642
Revert "Refactor: Make SymbolTags type-safe and adapt tag handling"
ratzdi Apr 29, 2026
88b5800
Reapply "Simplified filterSymbolTags:"
ratzdi Apr 29, 2026
0ffcf77
changed typecast from uint32_t to unsigned.
ratzdi Apr 29, 2026
c1005bb
changed typecast from uint32_t to unsigned.
ratzdi Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
/// Used to indicate the ClangdLSPServer is being destroyed.
std::atomic<bool> IsBeingDestroyed = {false};

// FIXME: The caching is a temporary solution to get corresponding clangd
// FIXME: The caching is a temporary solution to get corresponding clangd
// diagnostic from a LSP diagnostic.
// Ideally, ClangdServer can generate an identifier for each diagnostic,
// emit them via the LSP's data field (which was newly added in LSP 3.16).
Expand Down
131 changes: 127 additions & 4 deletions clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -135,6 +138,42 @@ bool isFinal(const Decl *D) {
return false;
}

// 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)
bool isOverrides(const NamedDecl *ND) {
if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(ND)) {
if (MD->size_overridden_methods() == 0)
return false;

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;
}
}
return false;
}

// 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
bool isImplements(const NamedDecl *ND) {
if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(ND)) {
if (MD->size_overridden_methods() == 0 || MD->isPureVirtual())
return false;

for (const auto *Overridden : MD->overridden_methods()) {
if (!Overridden->isPureVirtual())
return false;
}
return true;
}
return false;
}

// Indicates whether declaration D is a unique definition (as opposed to a
// declaration).
bool isUniqueDefinition(const NamedDecl *Decl) {
Expand All @@ -153,6 +192,51 @@ bool isUniqueDefinition(const NamedDecl *Decl) {
isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
isa<ObjCImplDecl>(Decl);
}

// Filter symbol tags based on the presence of other tags and the kind of
// symbol. This is needed to avoid redundant tags, e.g. Overrides implies
// Virtual and Implements implies Overrides/Virtual.
SymbolTags filterSymbolTags(SymbolTags ST) {
const SymbolTags VirtualMask = toSymbolTagBitmask(SymbolTag::Virtual);
const SymbolTags OverridesMask = toSymbolTagBitmask(SymbolTag::Overrides);
const SymbolTags ImplementsMask = toSymbolTagBitmask(SymbolTag::Implements);
const SymbolTags AbstractMask = toSymbolTagBitmask(SymbolTag::Abstract);
const SymbolTags FinalMask = toSymbolTagBitmask(SymbolTag::Final);

const SymbolTags RemoveVirtualAndOverrides = VirtualMask | OverridesMask;

// Implements implies both Overrides and Virtual.
if (ST & ImplementsMask)
ST &= ~RemoveVirtualAndOverrides;

// Final also suppresses both Virtual and Overrides in this model.
if (ST & FinalMask)
ST &= ~RemoveVirtualAndOverrides;

// Overrides or Abstract each imply Virtual.
if (ST & (OverridesMask | AbstractMask))
ST &= ~VirtualMask;

return ST;
}

bool isCXXClassMethod(const clang::clangd::Symbol &S) {
using clang::index::SymbolKind;
using clang::index::SymbolLanguage;

if (S.SymInfo.Lang != SymbolLanguage::CXX)
return false;

return llvm::is_contained({SymbolKind::InstanceMethod,
SymbolKind::StaticMethod, SymbolKind::Constructor,
SymbolKind::Destructor,
SymbolKind::ConversionFunction},
S.SymInfo.Kind);
}

template <typename E> constexpr E enumIncrement(E Value) {
return static_cast<E>(static_cast<std::underlying_type_t<E>>(Value) + 1);
}
} // namespace

SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
Expand All @@ -178,9 +262,15 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
if (isAbstract(&ND))
Result |= toSymbolTagBitmask(SymbolTag::Abstract);

if (isOverrides(&ND))
Result |= toSymbolTagBitmask(SymbolTag::Overrides);

if (isFinal(&ND))
Result |= toSymbolTagBitmask(SymbolTag::Final);

if (isImplements(&ND))
Result |= toSymbolTagBitmask(SymbolTag::Implements);

if (not isa<UnresolvedUsingValueDecl>(ND)) {
// Do not treat an UnresolvedUsingValueDecl as a declaration.
// It's more common to think of it as a reference to the
Expand Down Expand Up @@ -208,26 +298,58 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
return Result;
}

std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
const auto symbolTags = computeSymbolTags(ND);
std::vector<SymbolTag> expandTagBitmask(const SymbolTags STGS) {
Comment thread
timon-ul marked this conversation as resolved.
std::vector<SymbolTag> Tags;

if (symbolTags == 0)
if (STGS == 0)
return Tags;

// No filtering required since this function is only used for Symbols from the
// index, which have already been filtered in getSymbolTags(const NamedDecl
// &ND).

Comment thread
ratzdi marked this conversation as resolved.
// Iterate through SymbolTag enum values and collect any that are present in
// the bitmask. SymbolTag values are in the numeric range
// [FirstTag .. LastTag].
constexpr unsigned MinTag = static_cast<unsigned>(SymbolTag::FirstTag);
constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::LastTag);
for (unsigned I = MinTag; I <= MaxTag; ++I) {
auto ST = static_cast<SymbolTag>(I);
if (symbolTags & toSymbolTagBitmask(ST))
if (STGS & toSymbolTagBitmask(ST))
Tags.push_back(ST);
}
return Tags;
}

std::vector<SymbolTag> getSymbolTags(const Symbol &S) {
const SymbolTags Tags =
isCXXClassMethod(S) ? filterSymbolTags(S.Tags) : S.Tags;
return expandTagBitmask(Tags);
}

std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
const auto STGS = computeSymbolTags(ND);
SymbolTags FilteredTags = STGS;
std::vector<SymbolTag> Tags;

if (STGS == 0)
return Tags;

// Apply specific filter to the symbol tags only on CXX class methods.
if (isa<CXXMethodDecl>(ND))
FilteredTags = filterSymbolTags(STGS);

// Iterate through SymbolTag enum values and collect any that are present in
// the bitmask. SymbolTag values are in the numeric range
// [FirstTag .. LastTag].
for (SymbolTag Tag = SymbolTag::FirstTag; Tag <= SymbolTag::LastTag;
Tag = enumIncrement(Tag)) {
if (FilteredTags & toSymbolTagBitmask(Tag))
Tags.push_back(Tag);
}
return Tags;
}

namespace {
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
struct ScoredSymbolGreater {
Expand Down Expand Up @@ -359,6 +481,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
Info.score = Relevance.NameMatch > std::numeric_limits<float>::epsilon()
? Score / Relevance.NameMatch
: QualScore;
Info.tags = getSymbolTags(Sym);
Top.push({Score, std::move(Info)});
});
for (auto &R : std::move(Top).items())
Expand Down
27 changes: 17 additions & 10 deletions clang-tools-extra/clangd/FindSymbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,18 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H

#include "Protocol.h"
#include "index/Symbol.h"
#include "clang/AST/Decl.h"
#include "llvm/ADT/StringRef.h"

namespace clang {
class NamedDecl;

namespace clangd {
class ParsedAST;
class SymbolIndex;

/// A bitmask type representing symbol tags supported by LSP.
/// \see
/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#symbolTag
using SymbolTags = uint32_t;
/// Ensure we have enough bits to represent all SymbolTag values.
static_assert(static_cast<unsigned>(SymbolTag::LastTag) <= 32,
"Too many SymbolTags to fit in uint32_t. Change to uint64_t if "
"we ever have more than 32 tags.");
struct Symbol;
struct SymbolLocation;

/// Helper function for deriving an LSP Location from an index SymbolLocation.
llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,
Expand Down Expand Up @@ -69,6 +63,19 @@ SymbolTags computeSymbolTags(const NamedDecl &ND);
/// \p ND The declaration to get tags for.
std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);

/// Returns the \c SymbolTag values for the given indexed \p S.
///
/// Converts the bitmask stored in \c Symbol::Tags into a flat vector of
/// \c SymbolTag enum values. For C++ class methods (instance methods, static
/// methods, constructors, destructors, and conversion functions), a semantic
/// filter is applied first to remove tags that are implied by higher-priority
/// tags (e.g. \c Overrides implies \c Virtual, so \c Virtual is suppressed).
/// For all other symbol kinds the bitmask is expanded as-is.
///
/// \param S The indexed symbol whose tags should be returned.
/// \return A vector of \c SymbolTag values present in \c S.Tags, after
/// applying any applicable filters.
std::vector<SymbolTag> getSymbolTags(const Symbol &S);
} // namespace clangd
} // namespace clang

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,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);
}

Expand Down Expand Up @@ -1445,6 +1447,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);
}

Expand Down
22 changes: 18 additions & 4 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1115,22 +1115,33 @@ 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 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
// 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, // 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,
LastTag = ReadOnly
LastTag = Implements
};
llvm::json::Value toJSON(SymbolTag);
/// Represents programming constructs like variables, classes, interfaces etc.
Expand Down Expand Up @@ -1548,6 +1559,9 @@ struct TypeHierarchyItem {
/// The kind of this item.
SymbolKind kind;

/// The symbol tags for this item.
std::vector<SymbolTag> tags;

/// More detail for this item, e.g. the signature of a function.
std::optional<std::string> detail;

Expand Down
17 changes: 8 additions & 9 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1818,8 +1818,9 @@ 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.tags = getSymbolTags(ND);
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
HI.selectionRange = Range{NameBegin, NameEnd};
Expand Down Expand Up @@ -1872,6 +1873,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
HI.name = std::string(S.Name);
HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo);
HI.tags = getSymbolTags(S);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
// (https://github.com/clangd/clangd/issues/59).
Expand All @@ -1897,8 +1899,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;
}

Expand Down Expand Up @@ -2144,7 +2144,7 @@ static void unwrapFindType(
return;

// If there's a specific type alias, point at that rather than unwrapping.
if (const auto* TDT = T->getAs<TypedefType>())
if (const auto *TDT = T->getAs<TypedefType>())
return Out.push_back(QualType(TDT, 0));

// Pointers etc => pointee type.
Expand Down Expand Up @@ -2307,24 +2307,23 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,

std::optional<std::vector<TypeHierarchyItem>>
superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
std::vector<TypeHierarchyItem> Results;
if (!Item.data.parents)
if (!Index || !Item.data.parents)
return std::nullopt;
if (Item.data.parents->empty())
return Results;
LookupRequest Req;
llvm::DenseMap<SymbolID, const TypeHierarchyItem::ResolveParams *> IDToData;
for (const auto &Parent : *Item.data.parents) {
Req.IDs.insert(Parent.symbolID);
IDToData[Parent.symbolID] = &Parent;
}
std::vector<TypeHierarchyItem> Results;
Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) {
if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) {
THI->data = *IDToData.lookup(S.ID);
Results.emplace_back(std::move(*THI));
}
});
return Results;
return Results.empty() ? std::nullopt
: std::make_optional(std::move(Results));
}

std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/XRefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ std::vector<TypeHierarchyItem> getTypeHierarchy(
const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});

/// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside
/// the item.
/// the item. Returns nullopt if the item does not have parents or if
/// the index is not provided.
std::optional<std::vector<TypeHierarchyItem>>
superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
/// Returns direct children of a TypeHierarchyItem.
Expand Down
Loading