[clang-tools-extra] Symbol tags in SymbolInformation, WorkspaceSymbol, CallHierarchyItem and TypeHierarchyItem (PR #170103)

Dimitri Ratz via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 23 08:35:02 PST 2026


https://github.com/ratzdi updated https://github.com/llvm/llvm-project/pull/170103

>From 103b18fd135e523ee323ce6dc7e09d5ef2eb7f6a Mon Sep 17 00:00:00 2001
From: chouzz <zhouhua258 at outlook.com>
Date: Fri, 25 Oct 2024 17:42:04 +0800
Subject: [PATCH 01/42] [clangd] Support symbolTags for document symbol

---
 clang-tools-extra/clangd/AST.cpp              | 63 ++++++++++++++
 clang-tools-extra/clangd/AST.h                | 31 +++++++
 clang-tools-extra/clangd/FindSymbols.cpp      | 30 +++++++
 clang-tools-extra/clangd/Protocol.h           | 29 ++++++-
 .../clangd/SemanticHighlighting.cpp           | 85 -------------------
 5 files changed, 151 insertions(+), 87 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 0dcff2eae05e7..a4250bf9b313c 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -194,6 +194,69 @@ bool isImplementationDetail(const Decl *D) {
                             D->getASTContext().getSourceManager());
 }
 
+// Whether T is const in a loose sense - is a variable with this type readonly?
+bool isConst(QualType T) {
+  if (T.isNull())
+    return false;
+  T = T.getNonReferenceType();
+  if (T.isConstQualified())
+    return true;
+  if (const auto *AT = T->getAsArrayTypeUnsafe())
+    return isConst(AT->getElementType());
+  if (isConst(T->getPointeeType()))
+    return true;
+  return false;
+}
+
+bool isConst(const Decl *D) {
+  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
+    return true;
+  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
+      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
+    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
+      return true;
+  }
+  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
+    if (OCPD->isReadOnly())
+      return true;
+  }
+  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
+    if (!MPD->hasSetter())
+      return true;
+  }
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
+    if (CMD->isConst())
+      return true;
+  }
+  return false;
+}
+
+bool isStatic(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isStatic();
+  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
+    return VD->isStaticDataMember() || VD->isStaticLocal();
+  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
+    return OPD->isClassProperty();
+  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
+    return OMD->isClassMethod();
+  return false;
+}
+
+bool isAbstract(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isPureVirtual();
+  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasDefinition() && CRD->isAbstract();
+  return false;
+}
+
+bool isVirtual(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isVirtual();
+  return false;
+}
+
 SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) {
   auto L = D.getLocation();
   // For `- (void)foo` we want `foo` not the `-`.
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 2b83595e5b8e9..2fc0f8a7d2b6d 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -154,6 +154,37 @@ bool isImplicitTemplateInstantiation(const NamedDecl *D);
 ///   explicit specialization.
 bool isExplicitTemplateSpecialization(const NamedDecl *D);
 
+// Whether T is const in a loose sense - is a variable with this type readonly?
+bool isConst(QualType T);
+
+// Whether D is const in a loose sense (should it be highlighted as such?)
+// FIXME: This is separate from whether *a particular usage* can mutate D.
+//        We may want V in V.size() to be readonly even if V is mutable.
+bool isConst(const Decl *D);
+
+// "Static" means many things in C++, only some get the "static" modifier.
+//
+// Meanings that do:
+// - Members associated with the class rather than the instance.
+//   This is what 'static' most often means across languages.
+// - static local variables
+//   These are similarly "detached from their context" by the static keyword.
+//   In practice, these are rarely used inside classes, reducing confusion.
+//
+// Meanings that don't:
+// - Namespace-scoped variables, which have static storage class.
+//   This is implicit, so the keyword "static" isn't so strongly associated.
+//   If we want a modifier for these, "global scope" is probably the concept.
+// - Namespace-scoped variables/functions explicitly marked "static".
+//   There the keyword changes *linkage* , which is a totally different concept.
+//   If we want to model this, "file scope" would be a nice modifier.
+//
+// This is confusing, and maybe we should use another name, but because "static"
+// is a standard LSP modifier, having one with that name has advantages.
+bool isStatic(const Decl *D);
+bool isAbstract(const Decl *D);
+bool isVirtual(const Decl *D);
+
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.
 ///     void ns::something::foo() -> returns 'ns::something'
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 7655a39d5ba1f..5b04adeb1e1f2 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -188,6 +188,35 @@ std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {
   return printName(Ctx, ND);
 }
 
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND)
+{
+  std::vector<SymbolTag> Tags;
+  if (ND.isDeprecated()) 
+    Tags.push_back(SymbolTag::Deprecated);
+  if (isConst(&ND)) 
+      Tags.push_back(SymbolTag::Constant);
+  if (isStatic(&ND)) 
+      Tags.push_back(SymbolTag::Static);
+  if (const FieldDecl *FD = dyn_cast<FieldDecl>(&ND)) {
+    switch (FD->getAccess()) {
+      case AS_public:
+        Tags.push_back(SymbolTag::Public);
+        break;
+      case AS_protected:
+        Tags.push_back(SymbolTag::Protected);
+        break;
+      case AS_private:
+        Tags.push_back(SymbolTag::Private);
+        break;
+      default:
+        break;
+    }
+  } 
+  if (isVirtual(&ND))
+      Tags.push_back(SymbolTag::Virtual);
+  return Tags;
+}
+
 std::string getSymbolDetail(ASTContext &Ctx, const NamedDecl &ND) {
   PrintingPolicy P(Ctx.getPrintingPolicy());
   P.SuppressScope = true;
@@ -242,6 +271,7 @@ std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
   SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()),
                    sourceLocToPosition(SM, SymbolRange->getEnd())};
   SI.detail = getSymbolDetail(Ctx, ND);
+  SI.tags = getSymbolTags(ND);
 
   SourceLocation NameLoc = ND.getLocation();
   SourceLocation FallbackNameLoc;
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 2248572060431..2abdea4a86455 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1104,6 +1104,30 @@ struct CodeAction {
 };
 llvm::json::Value toJSON(const CodeAction &);
 
+enum class SymbolTag { 
+  Deprecated = 1 ,
+  Private = 2,
+  Package = 3,
+  Protected = 4,
+  Public = 5,
+  Internal= 6,
+  File = 7,
+  Static = 8,
+  Abstract = 9,
+  Final = 10,
+  Sealed = 11,
+  Constant = 12,
+  Transient = 13,
+  Volatile = 14,
+  Synchronized = 15,
+  Virtual = 16,
+  Nullable = 17,
+  NonNull = 18,
+  Declaration = 19,
+  Definition = 20,
+  ReadOnly = 21,
+};
+llvm::json::Value toJSON(SymbolTag);
 /// Represents programming constructs like variables, classes, interfaces etc.
 /// that appear in a document. Document symbols can be hierarchical and they
 /// have two ranges: one that encloses its definition and one that points to its
@@ -1121,6 +1145,9 @@ struct DocumentSymbol {
   /// Indicates if this symbol is deprecated.
   bool deprecated = false;
 
+  /// Tags for this symbol, e.g public, private, static, const etc.
+  std::vector<SymbolTag> tags;
+
   /// The range enclosing this symbol not including leading/trailing whitespace
   /// but everything else like comments. This information is typically used to
   /// determine if the clients cursor is inside the symbol to reveal in the
@@ -1572,8 +1599,6 @@ struct ResolveTypeHierarchyItemParams {
 bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &,
               llvm::json::Path);
 
-enum class SymbolTag { Deprecated = 1 };
-llvm::json::Value toJSON(SymbolTag);
 
 /// The parameter of a `textDocument/prepareCallHierarchy` request.
 struct CallHierarchyPrepareParams : public TextDocumentPositionParams {};
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index ab720ebe6b47f..49d844719eb6b 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -192,91 +192,6 @@ std::optional<HighlightingKind> kindForType(const Type *TP,
   return std::nullopt;
 }
 
-// Whether T is const in a loose sense - is a variable with this type readonly?
-bool isConst(QualType T) {
-  if (T.isNull())
-    return false;
-  T = T.getNonReferenceType();
-  if (T.isConstQualified())
-    return true;
-  if (const auto *AT = T->getAsArrayTypeUnsafe())
-    return isConst(AT->getElementType());
-  if (isConst(T->getPointeeType()))
-    return true;
-  return false;
-}
-
-// Whether D is const in a loose sense (should it be highlighted as such?)
-// FIXME: This is separate from whether *a particular usage* can mutate D.
-//        We may want V in V.size() to be readonly even if V is mutable.
-bool isConst(const Decl *D) {
-  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
-    return true;
-  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
-      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
-    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
-      return true;
-  }
-  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
-    if (OCPD->isReadOnly())
-      return true;
-  }
-  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
-    if (!MPD->hasSetter())
-      return true;
-  }
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
-    if (CMD->isConst())
-      return true;
-  }
-  return false;
-}
-
-// "Static" means many things in C++, only some get the "static" modifier.
-//
-// Meanings that do:
-// - Members associated with the class rather than the instance.
-//   This is what 'static' most often means across languages.
-// - static local variables
-//   These are similarly "detached from their context" by the static keyword.
-//   In practice, these are rarely used inside classes, reducing confusion.
-//
-// Meanings that don't:
-// - Namespace-scoped variables, which have static storage class.
-//   This is implicit, so the keyword "static" isn't so strongly associated.
-//   If we want a modifier for these, "global scope" is probably the concept.
-// - Namespace-scoped variables/functions explicitly marked "static".
-//   There the keyword changes *linkage* , which is a totally different concept.
-//   If we want to model this, "file scope" would be a nice modifier.
-//
-// This is confusing, and maybe we should use another name, but because "static"
-// is a standard LSP modifier, having one with that name has advantages.
-bool isStatic(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isStatic();
-  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
-    return VD->isStaticDataMember() || VD->isStaticLocal();
-  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
-    return OPD->isClassProperty();
-  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
-    return OMD->isClassMethod();
-  return false;
-}
-
-bool isAbstract(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isPureVirtual();
-  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
-    return CRD->hasDefinition() && CRD->isAbstract();
-  return false;
-}
-
-bool isVirtual(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isVirtual();
-  return false;
-}
-
 bool isDependent(const Decl *D) {
   if (isa<UnresolvedUsingValueDecl>(D))
     return true;

>From 18c71ac09c38b587b243ab0cb15bde80f0b7a590 Mon Sep 17 00:00:00 2001
From: chouzz <zhouhua258 at outlook.com>
Date: Mon, 28 Oct 2024 20:14:15 +0800
Subject: [PATCH 02/42] Fix access for class method

---
 clang-tools-extra/clangd/FindSymbols.cpp          | 6 ++++--
 clang-tools-extra/clangd/Protocol.cpp             | 2 ++
 clang-tools-extra/clangd/SemanticHighlighting.cpp | 1 +
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 5b04adeb1e1f2..c44b3339b435f 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -197,6 +197,9 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND)
       Tags.push_back(SymbolTag::Constant);
   if (isStatic(&ND)) 
       Tags.push_back(SymbolTag::Static);
+  if (isVirtual(&ND))
+      Tags.push_back(SymbolTag::Virtual);
+  
   if (const FieldDecl *FD = dyn_cast<FieldDecl>(&ND)) {
     switch (FD->getAccess()) {
       case AS_public:
@@ -212,8 +215,7 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND)
         break;
     }
   } 
-  if (isVirtual(&ND))
-      Tags.push_back(SymbolTag::Virtual);
+  
   return Tags;
 }
 
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 560b8e00ed377..9926f2dd63de5 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -964,6 +964,8 @@ llvm::json::Value toJSON(const DocumentSymbol &S) {
     Result["children"] = S.children;
   if (S.deprecated)
     Result["deprecated"] = true;
+  if (!S.tags.empty())
+    Result["tags"] = S.tags;
   // FIXME: workaround for older gcc/clang
   return std::move(Result);
 }
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 49d844719eb6b..53d9946859582 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "SemanticHighlighting.h"
+#include "AST.h"
 #include "Config.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"

>From af6491c6e68b6717192682951215afdb724cc847 Mon Sep 17 00:00:00 2001
From: chouzz <zhouhua258 at outlook.com>
Date: Thu, 31 Oct 2024 19:47:47 +0800
Subject: [PATCH 03/42] Support Declaration and Definition tags

---
 clang-tools-extra/clangd/AST.cpp              | 17 +++++++
 clang-tools-extra/clangd/AST.h                |  2 +-
 clang-tools-extra/clangd/FindSymbols.cpp      | 46 ++++++++++---------
 .../clangd/SemanticHighlighting.cpp           | 17 -------
 4 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index a4250bf9b313c..7de45986a0522 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -257,6 +257,23 @@ bool isVirtual(const Decl *D) {
   return false;
 }
 
+bool isUniqueDefinition(const NamedDecl *Decl) {
+  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
+    return Func->isThisDeclarationADefinition();
+  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
+    return Klass->isThisDeclarationADefinition();
+  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
+    return Iface->isThisDeclarationADefinition();
+  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
+    return Proto->isThisDeclarationADefinition();
+  if (auto *Var = dyn_cast<VarDecl>(Decl))
+    return Var->isThisDeclarationADefinition();
+  return isa<TemplateTypeParmDecl>(Decl) ||
+         isa<NonTypeTemplateParmDecl>(Decl) ||
+         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
+         isa<ObjCImplDecl>(Decl);
+}
+
 SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) {
   auto L = D.getLocation();
   // For `- (void)foo` we want `foo` not the `-`.
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 2fc0f8a7d2b6d..5fc667f3520db 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -184,7 +184,7 @@ bool isConst(const Decl *D);
 bool isStatic(const Decl *D);
 bool isAbstract(const Decl *D);
 bool isVirtual(const Decl *D);
-
+bool isUniqueDefinition(const NamedDecl *Decl);
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.
 ///     void ns::something::foo() -> returns 'ns::something'
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index c44b3339b435f..7d978a2849d9b 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -188,34 +188,36 @@ std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {
   return printName(Ctx, ND);
 }
 
-std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND)
-{
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   std::vector<SymbolTag> Tags;
-  if (ND.isDeprecated()) 
+  if (ND.isDeprecated())
     Tags.push_back(SymbolTag::Deprecated);
-  if (isConst(&ND)) 
-      Tags.push_back(SymbolTag::Constant);
-  if (isStatic(&ND)) 
-      Tags.push_back(SymbolTag::Static);
+  if (isConst(&ND))
+    Tags.push_back(SymbolTag::Constant);
+  if (isStatic(&ND))
+    Tags.push_back(SymbolTag::Static);
   if (isVirtual(&ND))
-      Tags.push_back(SymbolTag::Virtual);
-  
+    Tags.push_back(SymbolTag::Virtual);
+  if (!isa<UnresolvedUsingValueDecl>(ND))
+    Tags.push_back(SymbolTag::Declaration);
+  if (isUniqueDefinition(&ND))
+    Tags.push_back(SymbolTag::Definition);
   if (const FieldDecl *FD = dyn_cast<FieldDecl>(&ND)) {
     switch (FD->getAccess()) {
-      case AS_public:
-        Tags.push_back(SymbolTag::Public);
-        break;
-      case AS_protected:
-        Tags.push_back(SymbolTag::Protected);
-        break;
-      case AS_private:
-        Tags.push_back(SymbolTag::Private);
-        break;
-      default:
-        break;
+    case AS_public:
+      Tags.push_back(SymbolTag::Public);
+      break;
+    case AS_protected:
+      Tags.push_back(SymbolTag::Protected);
+      break;
+    case AS_private:
+      Tags.push_back(SymbolTag::Private);
+      break;
+    default:
+      break;
     }
-  } 
-  
+  }
+
   return Tags;
 }
 
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 53d9946859582..f53343951d7de 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -78,23 +78,6 @@ bool canHighlightName(DeclarationName Name) {
   llvm_unreachable("invalid name kind");
 }
 
-bool isUniqueDefinition(const NamedDecl *Decl) {
-  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
-    return Func->isThisDeclarationADefinition();
-  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
-    return Klass->isThisDeclarationADefinition();
-  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
-    return Iface->isThisDeclarationADefinition();
-  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
-    return Proto->isThisDeclarationADefinition();
-  if (auto *Var = dyn_cast<VarDecl>(Decl))
-    return Var->isThisDeclarationADefinition();
-  return isa<TemplateTypeParmDecl>(Decl) ||
-         isa<NonTypeTemplateParmDecl>(Decl) ||
-         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
-         isa<ObjCImplDecl>(Decl);
-}
-
 std::optional<HighlightingKind> kindForType(const Type *TP,
                                             const HeuristicResolver *Resolver);
 std::optional<HighlightingKind> kindForDecl(const NamedDecl *D,

>From baa29b1a6af59b0decf693cee9c4ddfec3f570ac Mon Sep 17 00:00:00 2001
From: chouzz <zhouhua258 at outlook.com>
Date: Mon, 18 Nov 2024 14:43:14 +0800
Subject: [PATCH 04/42] Remove constant tag

---
 clang-tools-extra/clangd/AST.cpp         |  4 +++
 clang-tools-extra/clangd/FindSymbols.cpp | 36 +++++++++++++-----------
 clang-tools-extra/clangd/Protocol.h      | 21 +++++++-------
 3 files changed, 33 insertions(+), 28 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 7de45986a0522..51ab1cdadd0e1 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -228,6 +228,8 @@ bool isConst(const Decl *D) {
     if (CMD->isConst())
       return true;
   }
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return isConst(FD->getReturnType());
   return false;
 }
 
@@ -240,6 +242,8 @@ bool isStatic(const Decl *D) {
     return OPD->isClassProperty();
   if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
     return OMD->isClassMethod();
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return FD->isStatic();
   return false;
 }
 
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 7d978a2849d9b..bdb79fde5a38a 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -193,29 +193,31 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   if (ND.isDeprecated())
     Tags.push_back(SymbolTag::Deprecated);
   if (isConst(&ND))
-    Tags.push_back(SymbolTag::Constant);
+    Tags.push_back(SymbolTag::ReadOnly);
   if (isStatic(&ND))
     Tags.push_back(SymbolTag::Static);
   if (isVirtual(&ND))
     Tags.push_back(SymbolTag::Virtual);
-  if (!isa<UnresolvedUsingValueDecl>(ND))
-    Tags.push_back(SymbolTag::Declaration);
+  if (isAbstract(&ND))
+    Tags.push_back(SymbolTag::Abstract);
+
   if (isUniqueDefinition(&ND))
     Tags.push_back(SymbolTag::Definition);
-  if (const FieldDecl *FD = dyn_cast<FieldDecl>(&ND)) {
-    switch (FD->getAccess()) {
-    case AS_public:
-      Tags.push_back(SymbolTag::Public);
-      break;
-    case AS_protected:
-      Tags.push_back(SymbolTag::Protected);
-      break;
-    case AS_private:
-      Tags.push_back(SymbolTag::Private);
-      break;
-    default:
-      break;
-    }
+  else if (!isa<UnresolvedUsingValueDecl>(ND))
+    Tags.push_back(SymbolTag::Declaration);
+
+  switch (ND.getAccess()) {
+  case AS_public:
+    Tags.push_back(SymbolTag::Public);
+    break;
+  case AS_protected:
+    Tags.push_back(SymbolTag::Protected);
+    break;
+  case AS_private:
+    Tags.push_back(SymbolTag::Private);
+    break;
+  default:
+    break;
   }
 
   return Tags;
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 2abdea4a86455..607551f1b2d9e 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1105,7 +1105,7 @@ struct CodeAction {
 llvm::json::Value toJSON(const CodeAction &);
 
 enum class SymbolTag { 
-  Deprecated = 1 ,
+  Deprecated = 1,
   Private = 2,
   Package = 3,
   Protected = 4,
@@ -1116,16 +1116,15 @@ enum class SymbolTag {
   Abstract = 9,
   Final = 10,
   Sealed = 11,
-  Constant = 12,
-  Transient = 13,
-  Volatile = 14,
-  Synchronized = 15,
-  Virtual = 16,
-  Nullable = 17,
-  NonNull = 18,
-  Declaration = 19,
-  Definition = 20,
-  ReadOnly = 21,
+  Transient = 12,
+  Volatile = 13,
+  Synchronized = 14,
+  Virtual = 15,
+  Nullable = 16,
+  NonNull = 17,
+  Declaration = 18,
+  Definition = 19,
+  ReadOnly = 20,
 };
 llvm::json::Value toJSON(SymbolTag);
 /// Represents programming constructs like variables, classes, interfaces etc.

>From 64d5a3642f17621fed8ad551378eca467694c538 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 11 Nov 2025 23:05:47 +0100
Subject: [PATCH 05/42] Fix test

---
 clang-tools-extra/clangd/test/symbols.test | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index af5d74123630e..40115f2496e50 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -56,7 +56,10 @@
 # CHECK-NEXT:            "character": {{.*}},
 # CHECK-NEXT:            "line": {{.*}}
 # CHECK-NEXT:          }
-# CHECK-NEXT:        }
+# CHECK-NEXT:        },
+# CHECK-NEXT:      "tags": [
+# CHECK-NEXT:        18
+# CHECK-NEXT:      ]
 # CHECK-NEXT:      },
 # CHECK-NEXT:      {
 # CHECK-NEXT:        "detail": "int ()",
@@ -81,7 +84,10 @@
 # CHECK-NEXT:            "character": {{.*}},
 # CHECK-NEXT:            "line": {{.*}}
 # CHECK-NEXT:          }
-# CHECK-NEXT:        }
+# CHECK-NEXT:        },
+# CHECK-NEXT:      "tags": [
+# CHECK-NEXT:        19
+# CHECK-NEXT:      ]
 # CHECK-NEXT:      }
 # CHECK-NEXT:    ]
 # CHECK-NEXT:}

>From 49551b5db5e1b7e6702d5691a44388c8be6d776c Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 12 Nov 2025 16:14:55 +0100
Subject: [PATCH 06/42] Tagging final methods and classes

---
 clang-tools-extra/clangd/AST.cpp         | 10 ++++++++++
 clang-tools-extra/clangd/AST.h           |  1 +
 clang-tools-extra/clangd/FindSymbols.cpp |  8 ++++++++
 3 files changed, 19 insertions(+)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 51ab1cdadd0e1..b0957a4c0fc01 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -261,6 +261,16 @@ bool isVirtual(const Decl *D) {
   return false;
 }
 
+bool isFinal(const Decl *D) {
+  if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
+
+  if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
+
+  return false;
+}
+
 bool isUniqueDefinition(const NamedDecl *Decl) {
   if (auto *Func = dyn_cast<FunctionDecl>(Decl))
     return Func->isThisDeclarationADefinition();
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 5fc667f3520db..8d31dbe93ef3b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -184,6 +184,7 @@ bool isConst(const Decl *D);
 bool isStatic(const Decl *D);
 bool isAbstract(const Decl *D);
 bool isVirtual(const Decl *D);
+bool isFinal(const Decl *D);
 bool isUniqueDefinition(const NamedDecl *Decl);
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index bdb79fde5a38a..e8a6bca17215c 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -190,17 +190,25 @@ std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {
 
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   std::vector<SymbolTag> Tags;
+
   if (ND.isDeprecated())
     Tags.push_back(SymbolTag::Deprecated);
+
   if (isConst(&ND))
     Tags.push_back(SymbolTag::ReadOnly);
+
   if (isStatic(&ND))
     Tags.push_back(SymbolTag::Static);
+
   if (isVirtual(&ND))
     Tags.push_back(SymbolTag::Virtual);
+
   if (isAbstract(&ND))
     Tags.push_back(SymbolTag::Abstract);
 
+  if (isFinal(&ND))
+    Tags.push_back(SymbolTag::Final);
+
   if (isUniqueDefinition(&ND))
     Tags.push_back(SymbolTag::Definition);
   else if (!isa<UnresolvedUsingValueDecl>(ND))

>From 8a4b3149a2d7336ba4be0d4b02639daa7a44cff3 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 13 Nov 2025 13:04:11 +0100
Subject: [PATCH 07/42] Checks the extraction of symbol tags

---
 .../clangd/test/symbol-tags.test              | 293 ++++++++++++++++++
 1 file changed, 293 insertions(+)
 create mode 100644 clang-tools-extra/clangd/test/symbol-tags.test

diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
new file mode 100644
index 0000000000000..3d4821ae32081
--- /dev/null
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -0,0 +1,293 @@
+# COM: Checks the extraction of symbol tags.
+
+# RUN: clangd -lit-test < %s | FileCheck %s
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"documentSymbol":{"hierarchicalDocumentSymbolSupport":true}},"workspace":{"symbol":{"symbolKind":{"valueSet": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}}}},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,
+    "text":
+    "class AbstractClass{\n public:\n virtual ~AbstractClass() = default;\n virtual void f1() = 0;\n void f2() const;\n protected: \n void f3(){}\n private: \n static void f4(){} \n }; void AbstractClass::f2() const {} \n class ImplClass final: public AbstractClass { \n public: \n void f1() final {}};"
+}}}
+---
+{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}}
+# CHECK:  "id": 2,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:    "result": [
+# CHECK-NEXT:      {
+# CHECK-NEXT:        "children": [
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "kind": 9,
+# CHECK-NEXT:          "name": "~AbstractClass",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 35,
+# CHECK-NEXT:              "line": 2
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 2
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 10,
+# CHECK-NEXT:              "line": 2
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 9,
+# CHECK-NEXT:              "line": 2
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            15,
+# CHECK-NEXT:            19,
+# CHECK-NEXT:            5
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        },
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "detail": "void ()",
+# CHECK-NEXT:          "kind": 6,
+# CHECK-NEXT:          "name": "f1",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 22,
+# CHECK-NEXT:              "line": 3
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 3
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 16,
+# CHECK-NEXT:              "line": 3
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 14,
+# CHECK-NEXT:              "line": 3
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            15,
+# CHECK-NEXT:            9,
+# CHECK-NEXT:            18,
+# CHECK-NEXT:            5
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        },
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "detail": "void () const",
+# CHECK-NEXT:          "kind": 6,
+# CHECK-NEXT:          "name": "f2",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 16,
+# CHECK-NEXT:              "line": 4
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 4
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 8,
+# CHECK-NEXT:              "line": 4
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 6,
+# CHECK-NEXT:              "line": 4
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            20,
+# CHECK-NEXT:            18,
+# CHECK-NEXT:            5
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        },
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "detail": "void ()",
+# CHECK-NEXT:          "kind": 6,
+# CHECK-NEXT:          "name": "f3",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 12,
+# CHECK-NEXT:              "line": 6
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 6
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 8,
+# CHECK-NEXT:              "line": 6
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 6,
+# CHECK-NEXT:              "line": 6
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            19,
+# CHECK-NEXT:            4
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        },
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "detail": "void ()",
+# CHECK-NEXT:          "kind": 6,
+# CHECK-NEXT:          "name": "f4",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 19,
+# CHECK-NEXT:              "line": 8
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 8
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 15,
+# CHECK-NEXT:              "line": 8
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 13,
+# CHECK-NEXT:              "line": 8
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            8,
+# CHECK-NEXT:            19,
+# CHECK-NEXT:            2
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        }
+# CHECK-NEXT:      ],
+# CHECK-NEXT:      "detail": "class",
+# CHECK-NEXT:      "kind": 5,
+# CHECK-NEXT:      "name": "AbstractClass",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 2,
+# CHECK-NEXT:          "line": 9
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 0,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "selectionRange": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 19,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 6,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "tags": [
+# CHECK-NEXT:        9,
+# CHECK-NEXT:        19
+# CHECK-NEXT:      ]
+# CHECK-NEXT:    },
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "detail": "void () const",
+# CHECK-NEXT:      "kind": 6,
+# CHECK-NEXT:      "name": "AbstractClass::f2",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 37,
+# CHECK-NEXT:          "line": 9
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 4,
+# CHECK-NEXT:          "line": 9
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "selectionRange": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 26,
+# CHECK-NEXT:          "line": 9
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 24,
+# CHECK-NEXT:          "line": 9
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "tags": [
+# CHECK-NEXT:        20,
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        5
+# CHECK-NEXT:      ]
+# CHECK-NEXT:    },
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "children": [
+# CHECK-NEXT:        {
+# CHECK-NEXT:          "detail": "void ()",
+# CHECK-NEXT:          "kind": 6,
+# CHECK-NEXT:          "name": "f1",
+# CHECK-NEXT:          "range": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 19,
+# CHECK-NEXT:              "line": 12
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 1,
+# CHECK-NEXT:              "line": 12
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "selectionRange": {
+# CHECK-NEXT:            "end": {
+# CHECK-NEXT:              "character": 8,
+# CHECK-NEXT:              "line": 12
+# CHECK-NEXT:            },
+# CHECK-NEXT:            "start": {
+# CHECK-NEXT:              "character": 6,
+# CHECK-NEXT:              "line": 12
+# CHECK-NEXT:            }
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            15,
+# CHECK-NEXT:            10,
+# CHECK-NEXT:            19,
+# CHECK-NEXT:            5
+# CHECK-NEXT:          ]
+# CHECK-NEXT:        }
+# CHECK-NEXT:      ],
+# CHECK-NEXT:      "detail": "class",
+# CHECK-NEXT:      "kind": 5,
+# CHECK-NEXT:      "name": "ImplClass",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 20,
+# CHECK-NEXT:          "line": 12
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 1,
+# CHECK-NEXT:          "line": 10
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "selectionRange": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 16,
+# CHECK-NEXT:          "line": 10
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 7,
+# CHECK-NEXT:          "line": 10
+# CHECK-NEXT:        }
+# CHECK-NEXT:      },
+# CHECK-NEXT:      "tags": [
+# CHECK-NEXT:        10,
+# CHECK-NEXT:        19
+# CHECK-NEXT:      ]
+# CHECK-NEXT:    }
+# CHECK-NEXT:  ]
+# CHECK-NEXT:}
+
+---
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}

>From 49574d8671be536ca8c056f309dfbe8ec2b4754e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 13 Nov 2025 16:40:17 +0100
Subject: [PATCH 08/42] Docu update

---
 clang-tools-extra/clangd/AST.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 8d31dbe93ef3b..ef1c95d26153e 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -182,9 +182,13 @@ bool isConst(const Decl *D);
 // This is confusing, and maybe we should use another name, but because "static"
 // is a standard LSP modifier, having one with that name has advantages.
 bool isStatic(const Decl *D);
+// Indicates whether declaration D is abstract in cases where D is a struct or a class.
 bool isAbstract(const Decl *D);
+// Indicates whether declaration D is virtual in cases where D is a method.
 bool isVirtual(const Decl *D);
+// Indicates whether declaration D is final in cases where D is a struct, class or method.
 bool isFinal(const Decl *D);
+// Indicates whether declaration D is a unique definition (as opposed to a declaration).
 bool isUniqueDefinition(const NamedDecl *Decl);
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.

>From a5a85556ad05fc68dab7ca311a68c784e7c8891f Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 18 Nov 2025 11:02:49 +0100
Subject: [PATCH 09/42] Fix code format.

---
 clang-tools-extra/clangd/AST.h      |  9 ++++++---
 clang-tools-extra/clangd/Protocol.h | 23 +++++++++++------------
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index ef1c95d26153e..3b4e6eb43c44b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -182,13 +182,16 @@ bool isConst(const Decl *D);
 // This is confusing, and maybe we should use another name, but because "static"
 // is a standard LSP modifier, having one with that name has advantages.
 bool isStatic(const Decl *D);
-// Indicates whether declaration D is abstract in cases where D is a struct or a class.
+// Indicates whether declaration D is abstract in cases where D is a struct or a
+// class.
 bool isAbstract(const Decl *D);
 // Indicates whether declaration D is virtual in cases where D is a method.
 bool isVirtual(const Decl *D);
-// Indicates whether declaration D is final in cases where D is a struct, class or method.
+// Indicates whether declaration D is final in cases where D is a struct, class
+// or method.
 bool isFinal(const Decl *D);
-// Indicates whether declaration D is a unique definition (as opposed to a declaration).
+// Indicates whether declaration D is a unique definition (as opposed to a
+// declaration).
 bool isUniqueDefinition(const NamedDecl *Decl);
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 607551f1b2d9e..43e68dd38c57d 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -281,7 +281,7 @@ struct TextDocumentEdit {
   /// The text document to change.
   VersionedTextDocumentIdentifier textDocument;
 
-	/// The edits to be applied.
+  /// The edits to be applied.
   /// FIXME: support the AnnotatedTextEdit variant.
   std::vector<TextEdit> edits;
 };
@@ -560,7 +560,7 @@ struct ClientCapabilities {
 
   /// The client supports versioned document changes for WorkspaceEdit.
   bool DocumentChanges = false;
-  
+
   /// The client supports change annotations on text edits,
   bool ChangeAnnotation = false;
 
@@ -1027,12 +1027,12 @@ struct WorkspaceEdit {
   /// Versioned document edits.
   ///
   /// If a client neither supports `documentChanges` nor
-	/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
-	/// using the `changes` property are supported.
+  /// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
+  /// using the `changes` property are supported.
   std::optional<std::vector<TextDocumentEdit>> documentChanges;
-  
+
   /// A map of change annotations that can be referenced in
-	/// AnnotatedTextEdit.
+  /// AnnotatedTextEdit.
   std::map<std::string, ChangeAnnotation> changeAnnotations;
 };
 bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path);
@@ -1104,13 +1104,13 @@ struct CodeAction {
 };
 llvm::json::Value toJSON(const CodeAction &);
 
-enum class SymbolTag { 
+enum class SymbolTag {
   Deprecated = 1,
   Private = 2,
   Package = 3,
   Protected = 4,
   Public = 5,
-  Internal= 6,
+  Internal = 6,
   File = 7,
   Static = 8,
   Abstract = 9,
@@ -1314,13 +1314,13 @@ enum class InsertTextFormat {
 /// Additional details for a completion item label.
 struct CompletionItemLabelDetails {
   /// An optional string which is rendered less prominently directly after label
-	/// without any spacing. Should be used for function signatures or type
+  /// without any spacing. Should be used for function signatures or type
   /// annotations.
   std::string detail;
 
   /// An optional string which is rendered less prominently after
-	/// CompletionItemLabelDetails.detail. Should be used for fully qualified
-	/// names or file path.
+  /// CompletionItemLabelDetails.detail. Should be used for fully qualified
+  /// names or file path.
   std::string description;
 };
 llvm::json::Value toJSON(const CompletionItemLabelDetails &);
@@ -1598,7 +1598,6 @@ struct ResolveTypeHierarchyItemParams {
 bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &,
               llvm::json::Path);
 
-
 /// The parameter of a `textDocument/prepareCallHierarchy` request.
 struct CallHierarchyPrepareParams : public TextDocumentPositionParams {};
 

>From 8131e6f09e7d51bcfd992f7c8817980e6f862172 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 27 Nov 2025 15:13:55 +0100
Subject: [PATCH 10/42] Fill symbol tags for the response CallHierarchyItem of
 prepareCallHierarchy.

---
 clang-tools-extra/clangd/FindSymbols.cpp | 86 ++++++++++++------------
 clang-tools-extra/clangd/FindSymbols.h   |  5 ++
 clang-tools-extra/clangd/Protocol.h      |  8 ++-
 clang-tools-extra/clangd/XRefs.cpp       |  1 +
 4 files changed, 56 insertions(+), 44 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index e8a6bca17215c..28bee28634aeb 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -30,6 +30,49 @@
 namespace clang {
 namespace clangd {
 
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
+  std::vector<SymbolTag> Tags;
+
+  if (ND.isDeprecated())
+    Tags.push_back(SymbolTag::Deprecated);
+
+  if (isConst(&ND))
+    Tags.push_back(SymbolTag::ReadOnly);
+
+  if (isStatic(&ND))
+    Tags.push_back(SymbolTag::Static);
+
+  if (isVirtual(&ND))
+    Tags.push_back(SymbolTag::Virtual);
+
+  if (isAbstract(&ND))
+    Tags.push_back(SymbolTag::Abstract);
+
+  if (isFinal(&ND))
+    Tags.push_back(SymbolTag::Final);
+
+  if (isUniqueDefinition(&ND))
+    Tags.push_back(SymbolTag::Definition);
+  else if (!isa<UnresolvedUsingValueDecl>(ND))
+    Tags.push_back(SymbolTag::Declaration);
+
+  switch (ND.getAccess()) {
+  case AS_public:
+    Tags.push_back(SymbolTag::Public);
+    break;
+  case AS_protected:
+    Tags.push_back(SymbolTag::Protected);
+    break;
+  case AS_private:
+    Tags.push_back(SymbolTag::Private);
+    break;
+  default:
+    break;
+  }
+
+  return Tags;
+}
+
 namespace {
 using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
 struct ScoredSymbolGreater {
@@ -188,49 +231,6 @@ std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {
   return printName(Ctx, ND);
 }
 
-std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
-  std::vector<SymbolTag> Tags;
-
-  if (ND.isDeprecated())
-    Tags.push_back(SymbolTag::Deprecated);
-
-  if (isConst(&ND))
-    Tags.push_back(SymbolTag::ReadOnly);
-
-  if (isStatic(&ND))
-    Tags.push_back(SymbolTag::Static);
-
-  if (isVirtual(&ND))
-    Tags.push_back(SymbolTag::Virtual);
-
-  if (isAbstract(&ND))
-    Tags.push_back(SymbolTag::Abstract);
-
-  if (isFinal(&ND))
-    Tags.push_back(SymbolTag::Final);
-
-  if (isUniqueDefinition(&ND))
-    Tags.push_back(SymbolTag::Definition);
-  else if (!isa<UnresolvedUsingValueDecl>(ND))
-    Tags.push_back(SymbolTag::Declaration);
-
-  switch (ND.getAccess()) {
-  case AS_public:
-    Tags.push_back(SymbolTag::Public);
-    break;
-  case AS_protected:
-    Tags.push_back(SymbolTag::Protected);
-    break;
-  case AS_private:
-    Tags.push_back(SymbolTag::Private);
-    break;
-  default:
-    break;
-  }
-
-  return Tags;
-}
-
 std::string getSymbolDetail(ASTContext &Ctx, const NamedDecl &ND) {
   PrintingPolicy P(Ctx.getPrintingPolicy());
   P.SuppressScope = true;
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 5fb116b13d113..075bf09f1a75b 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -15,6 +15,7 @@
 #include "Protocol.h"
 #include "index/Symbol.h"
 #include "llvm/ADT/StringRef.h"
+#include "clang/AST/Decl.h"
 
 namespace clang {
 namespace clangd {
@@ -47,6 +48,10 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
 /// same order that they appear.
 llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 
+/// Returns the symbol tags for the given declaration.
+/// \p ND The declaration to get tags for.
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 43e68dd38c57d..b8d18ce9eee79 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1144,7 +1144,7 @@ struct DocumentSymbol {
   /// Indicates if this symbol is deprecated.
   bool deprecated = false;
 
-  /// Tags for this symbol, e.g public, private, static, const etc.
+  /// The tags for this symbol.
   std::vector<SymbolTag> tags;
 
   /// The range enclosing this symbol not including leading/trailing whitespace
@@ -1172,6 +1172,9 @@ struct SymbolInformation {
   /// The kind of this symbol.
   SymbolKind kind;
 
+  /// Tags for this symbol, e.g public, private, static, const etc.
+  std::vector<SymbolTag> tags;
+
   /// The location of this symbol.
   Location location;
 
@@ -1539,6 +1542,9 @@ struct TypeHierarchyItem {
   /// The kind of this item.
   SymbolKind kind;
 
+  /// The symbol tags for this item.
+  std::vector<SymbolTag> tags;
+
   /// More detail for this item, e.g. the signature of a function.
   std::optional<std::string> detail;
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index ef45acf501612..ced5f5b21be81 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1781,6 +1781,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   HI.name = printName(Ctx, ND);
   // FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
   HI.kind = SK;
+  HI.tags = getSymbolTags(ND);
   HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
                    sourceLocToPosition(SM, DeclRange->getEnd())};
   HI.selectionRange = Range{NameBegin, NameEnd};

>From 317eb1d3c0d685c5b46b6b477dc9e74ef7f8d79e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 28 Nov 2025 09:06:40 +0100
Subject: [PATCH 11/42] Fix call-hierarchy.test

Added tags node in the expected JSON output.
---
 clang-tools-extra/clangd/test/call-hierarchy.test | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index 6548ea0068a8d..a25731f03b4c5 100644
--- a/clang-tools-extra/clangd/test/call-hierarchy.test
+++ b/clang-tools-extra/clangd/test/call-hierarchy.test
@@ -31,6 +31,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:    }
 ---

>From ff4433c37e72e673618aa6c33a780dc6bfb36297 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 28 Nov 2025 14:01:25 +0100
Subject: [PATCH 12/42] 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 28bee28634aeb..217ed7ee83555 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -73,6 +73,20 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   return Tags;
 }
 
+std::vector<SymbolTag> getSymbolTags(const Symbol &S) {
+  std::vector<SymbolTag> Tags;
+
+  if (S.Flags & Symbol::Deprecated)
+    Tags.push_back(SymbolTag::Deprecated);
+
+  if (S.Definition)
+    Tags.push_back(SymbolTag::Definition);
+  else
+    Tags.push_back(SymbolTag::Declaration);
+
+  return Tags;
+}
+
 namespace {
 using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
 struct ScoredSymbolGreater {
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 075bf09f1a75b..36c939a4d1efa 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -52,6 +52,9 @@ llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 /// \p ND The declaration to get tags for.
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
 
+/// Returns the symbol tags for an index `Symbol`.
+std::vector<SymbolTag> getSymbolTags(const Symbol &S);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 9926f2dd63de5..5ff92fca933ff 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1437,6 +1437,8 @@ llvm::json::Value toJSON(const TypeHierarchyItem &I) {
 
   if (I.detail)
     Result["detail"] = I.detail;
+  if(!I.tags.empty())
+    Result["tags"] = I.tags;
   return std::move(Result);
 }
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index ced5f5b21be81..c8be48767a01e 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1834,6 +1834,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
   HI.name = std::string(S.Name);
   HI.detail = (S.Scope + S.Name).str();
   HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
+  HI.tags = getSymbolTags(S);
   HI.selectionRange = Loc->range;
   // FIXME: Populate 'range' correctly
   // (https://github.com/clangd/clangd/issues/59).
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index a5f13ab13d0b3..918a37a74098c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -44,6 +44,9 @@
 # CHECK-NEXT:           "line": 2
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
+# CHECK-NEXT:       "tags": [
+# CHECK-NEXT:         19
+# CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
 # CHECK-NEXT:   ]
@@ -85,6 +88,9 @@
 # CHECK-NEXT:           "line": 1
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
+# CHECK-NEXT:       "tags": [
+# CHECK-NEXT:         19
+# CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
 # CHECK-NEXT:  ]
@@ -136,6 +142,9 @@
 # CHECK-NEXT:           "line": 3
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
+# CHECK-NEXT:       "tags": [
+# CHECK-NEXT:         19
+# CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
 # CHECK-NEXT:  ]

>From b8c30108391f8aa5b4217ca600650f487e54c67a Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 1 Dec 2025 11:34:51 +0100
Subject: [PATCH 13/42] Fill symbol tags into the object SymbolInformation.

---
 clang-tools-extra/clangd/Protocol.cpp      | 2 ++
 clang-tools-extra/clangd/test/symbols.test | 5 ++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

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 40115f2496e50..89862afbff44e 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 6a01f070e977ead790c2cc68c040e582ecf7d055 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 1 Dec 2025 11:39:39 +0100
Subject: [PATCH 14/42] Fill symbol tags into the object SymbolInformation on
 getting workspace symbols.

---
 clang-tools-extra/clangd/FindSymbols.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 217ed7ee83555..2927fdf63dd24 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -218,6 +218,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())

>From 7fadc93a1efaa36326063583d25c658331d90e54 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 1 Dec 2025 12:03:35 +0100
Subject: [PATCH 15/42] Minor improvements.

---
 clang-tools-extra/clangd/FindSymbols.cpp | 4 +++-
 clang-tools-extra/clangd/FindSymbols.h   | 6 ++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 2927fdf63dd24..6d8497841e1bc 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"
@@ -23,7 +26,6 @@
 #include "llvm/ADT/StringRef.h"
 #include <limits>
 #include <optional>
-#include <tuple>
 
 #define DEBUG_TYPE "FindSymbols"
 
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 36c939a4d1efa..696c57de50bea 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -13,14 +13,16 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H
 
 #include "Protocol.h"
-#include "index/Symbol.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;
 
 /// Helper function for deriving an LSP Location from an index SymbolLocation.
 llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,

>From 60127f316c6d29251f1b6c46695e2950949f0dbb Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 2 Dec 2025 12:36:46 +0100
Subject: [PATCH 16/42] Removed duplicate tag insertion.

---
 clang-tools-extra/clangd/XRefs.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index c8be48767a01e..ff13a31f5e354 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1860,8 +1860,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 8e1ead464c8375cfce196a59c5086ee7c6c8ee25 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 3 Dec 2025 13:09:58 +0100
Subject: [PATCH 17/42] Fill 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 ff13a31f5e354..4192f4e2f2df4 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1779,7 +1779,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.tags = getSymbolTags(ND);
   HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index a25731f03b4c5..aba1418e3ec84 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 a9f972db38f88896f9d695b18ec1d91a4bf96e19 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 12 Dec 2025 12:17:56 +0100
Subject: [PATCH 18/42] 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 f8e6da73bbb1f..be328a86d5177 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1387,7 +1387,7 @@ void ClangdLSPServer::onPrepareCallHierarchy(
 void ClangdLSPServer::onCallHierarchyIncomingCalls(
     const CallHierarchyIncomingCallsParams &Params,
     Callback<std::vector<CallHierarchyIncomingCall>> Reply) {
-  Server->incomingCalls(Params.item, std::move(Reply));
+  Server->incomingCalls(Params.item.uri.file(), Params.item, std::move(Reply));
 }
 
 void ClangdLSPServer::onClangdInlayHints(const InlayHintsParams &Params,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..ab5fae7384c22 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -908,12 +908,16 @@ void ClangdServer::prepareCallHierarchy(
 }
 
 void ClangdServer::incomingCalls(
+    PathRef File,
     const CallHierarchyItem &Item,
     Callback<std::vector<CallHierarchyIncomingCall>> CB) {
-  WorkScheduler->run("Incoming Calls", "",
-                     [CB = std::move(CB), Item, this]() mutable {
-                       CB(clangd::incomingCalls(Item, Index));
-                     });
+  auto Action = [Item, CB = std::move(CB), this](
+      llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(clangd::incomingCalls(Item, Index, InpAST->AST));
+  };
+  WorkScheduler->runWithAST("Incoming Calls", File, std::move(Action));
 }
 
 void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4a1eae188f7eb..37ebed8905423 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -294,8 +294,9 @@ class ClangdServer {
                             Callback<std::vector<CallHierarchyItem>> CB);
 
   /// Resolve incoming calls for a given call hierarchy item.
-  void incomingCalls(const CallHierarchyItem &Item,
-                     Callback<std::vector<CallHierarchyIncomingCall>>);
+  void incomingCalls(PathRef File,
+                     const CallHierarchyItem &Item,
+                     Callback<std::vector<CallHierarchyIncomingCall>> CB);
 
   /// Resolve outgoing calls for a given call hierarchy item.
   void outgoingCalls(const CallHierarchyItem &Item,
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 4192f4e2f2df4..3ae2be110e610 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -2311,6 +2311,36 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                Item.uri.file());
 }
 
+// Tries to find a NamedDecl in the AST that matches the given Symbol.
+// Returns nullptr if the symbol is not found in the current AST.
+const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
+                                        const ParsedAST &AST) {
+  // Try to convert the symbol to a location and find the decl at that location
+  auto SymLoc = symbolToLocation(Sym, AST.tuPath());
+  if (!SymLoc)
+    return nullptr;
+
+  // Check if the symbol location is in the main file
+  if (SymLoc->uri.file() != AST.tuPath())
+    return nullptr;
+
+  // Convert LSP position to source location
+  const auto &SM = AST.getSourceManager();
+  auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
+  if (!CurLoc) {
+    llvm::consumeError(CurLoc.takeError());
+    return nullptr;
+  }
+
+  // Get all decls at this location
+  auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
+  if (Decls.empty())
+    return nullptr;
+
+  // Return the first decl (usually the most specific one)
+  return Decls[0];
+}
+
 std::vector<CallHierarchyItem>
 prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
   std::vector<CallHierarchyItem> Result;
@@ -2338,8 +2368,10 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) {
 }
 
 std::vector<CallHierarchyIncomingCall>
-incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
+incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+              const ParsedAST &AST) {
   std::vector<CallHierarchyIncomingCall> Results;
+
   if (!Index || Item.data.empty())
     return Results;
   auto ID = SymbolID::fromStr(Item.data);
@@ -2383,14 +2415,27 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
     Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
       auto It = CallsIn.find(Caller.ID);
       assert(It != CallsIn.end());
-      if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
+      if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) {
+        if (auto CHI = declToCallHierarchyItem(*ND, AST.tuPath())) {
+          std::vector<Range> FromRanges;
+          for (const Location &L : It->second) {
+            if (L.uri != CHI->uri) {
+              // Call location not in same file as caller.
+              // This can happen in some edge cases. There's not much we can do,
+              // since the protocol only allows returning ranges interpreted as
+              // being in the caller's file.
+              continue;
+            }
+            FromRanges.push_back(L.range);
+          }
+          Results.push_back(CallHierarchyIncomingCall{
+              std::move(*CHI), std::move(FromRanges), MightNeverCall});
+        }
+      } else if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
+        // Fallback to using symbol if NamedDecl is not available
         std::vector<Range> FromRanges;
         for (const Location &L : It->second) {
           if (L.uri != CHI->uri) {
-            // Call location not in same file as caller.
-            // This can happen in some edge cases. There's not much we can do,
-            // since the protocol only allows returning ranges interpreted as
-            // being in the caller's file.
             continue;
           }
           FromRanges.push_back(L.range);
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 247e52314c3f9..1019fa189a613 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -148,7 +148,8 @@ std::vector<CallHierarchyItem>
 prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
 
 std::vector<CallHierarchyIncomingCall>
-incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
+incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+              const ParsedAST &AST);
 
 std::vector<CallHierarchyOutgoingCall>
 outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 9859577c7cf7e..f5e9983aa70ec 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -89,12 +89,12 @@ TEST(CallHierarchy, IncomingOneFileCpp) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(
       IncomingLevel1,
       ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
                         iFromRanges(Source.range("Callee")))));
-  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
   ASSERT_THAT(
       IncomingLevel2,
       ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
@@ -103,13 +103,13 @@ TEST(CallHierarchy, IncomingOneFileCpp) {
                   AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
                         iFromRanges(Source.range("Caller1C")))));
 
-  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
   ASSERT_THAT(
       IncomingLevel3,
       ElementsAre(AllOf(from(AllOf(withName("caller3"), withDetail("caller3"))),
                         iFromRanges(Source.range("Caller2")))));
 
-  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
   EXPECT_THAT(IncomingLevel4, IsEmpty());
 }
 
@@ -137,12 +137,12 @@ TEST(CallHierarchy, IncomingOneFileObjC) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(AllOf(withName("caller1"),
                                            withDetail("MyClass::caller1"))),
                                 iFromRanges(Source.range("Callee")))));
-  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
   ASSERT_THAT(IncomingLevel2,
               ElementsAre(AllOf(from(AllOf(withName("caller2"),
                                            withDetail("MyClass::caller2"))),
@@ -152,13 +152,13 @@ TEST(CallHierarchy, IncomingOneFileObjC) {
                                            withDetail("MyClass::caller3"))),
                                 iFromRanges(Source.range("Caller1C")))));
 
-  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
   ASSERT_THAT(IncomingLevel3,
               ElementsAre(AllOf(from(AllOf(withName("caller3"),
                                            withDetail("MyClass::caller3"))),
                                 iFromRanges(Source.range("Caller2")))));
 
-  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+  auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
   EXPECT_THAT(IncomingLevel4, IsEmpty());
 }
 
@@ -184,18 +184,18 @@ TEST(CallHierarchy, IncomingIncludeOverrides) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(AllOf(withName("Func"),
                                            withDetail("Implementation::Func"))),
                                 iFromRanges(Source.range("Callee")))));
-  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
   ASSERT_THAT(
       IncomingLevel2,
       ElementsAre(AllOf(from(AllOf(withName("Test"), withDetail("Test"))),
                         iFromRanges(Source.range("FuncCall")))));
 
-  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+  auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
   EXPECT_THAT(IncomingLevel3, IsEmpty());
 }
 
@@ -221,13 +221,13 @@ TEST(CallHierarchy, MainFileOnlyRef) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(
       IncomingLevel1,
       ElementsAre(AllOf(from(AllOf(withName("caller1"), withDetail("caller1"))),
                         iFromRanges(Source.range("Callee")))));
 
-  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+  auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
   EXPECT_THAT(
       IncomingLevel2,
       ElementsAre(AllOf(from(AllOf(withName("caller2"), withDetail("caller2"))),
@@ -256,7 +256,7 @@ TEST(CallHierarchy, IncomingQualified) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("Waldo::find")));
-  auto Incoming = incomingCalls(Items[0], Index.get());
+  auto Incoming = incomingCalls(Items[0], Index.get(), AST);
   EXPECT_THAT(
       Incoming,
       ElementsAre(
@@ -396,13 +396,13 @@ TEST(CallHierarchy, MultiFileCpp) {
     std::vector<CallHierarchyItem> Items =
         prepareCallHierarchy(AST, Pos, TUPath);
     ASSERT_THAT(Items, ElementsAre(withName("callee")));
-    auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+    auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
     ASSERT_THAT(IncomingLevel1,
                 ElementsAre(AllOf(from(AllOf(withName("caller1"),
                                              withDetail("nsa::caller1"))),
                                   iFromRanges(Caller1C.range()))));
 
-    auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+    auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
     ASSERT_THAT(
         IncomingLevel2,
         ElementsAre(
@@ -411,13 +411,13 @@ TEST(CallHierarchy, MultiFileCpp) {
             AllOf(from(AllOf(withName("caller3"), withDetail("nsa::caller3"))),
                   iFromRanges(Caller3C.range("Caller1")))));
 
-    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
     ASSERT_THAT(IncomingLevel3,
                 ElementsAre(AllOf(from(AllOf(withName("caller3"),
                                              withDetail("nsa::caller3"))),
                                   iFromRanges(Caller3C.range("Caller2")))));
 
-    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
     EXPECT_THAT(IncomingLevel4, IsEmpty());
   };
 
@@ -553,12 +553,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
     std::vector<CallHierarchyItem> Items =
         prepareCallHierarchy(AST, Pos, TUPath);
     ASSERT_THAT(Items, ElementsAre(withName("callee")));
-    auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+    auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
     ASSERT_THAT(IncomingLevel1,
                 ElementsAre(AllOf(from(withName("caller1")),
                                   iFromRanges(Caller1C.range()))));
 
-    auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get());
+    auto IncomingLevel2 = incomingCalls(IncomingLevel1[0].from, Index.get(), AST);
     ASSERT_THAT(IncomingLevel2,
                 ElementsAre(AllOf(from(withName("caller2")),
                                   iFromRanges(Caller2C.range("A"),
@@ -566,12 +566,12 @@ TEST(CallHierarchy, IncomingMultiFileObjC) {
                             AllOf(from(withName("caller3")),
                                   iFromRanges(Caller3C.range("Caller1")))));
 
-    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get());
+    auto IncomingLevel3 = incomingCalls(IncomingLevel2[0].from, Index.get(), AST);
     ASSERT_THAT(IncomingLevel3,
                 ElementsAre(AllOf(from(withName("caller3")),
                                   iFromRanges(Caller3C.range("Caller2")))));
 
-    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get());
+    auto IncomingLevel4 = incomingCalls(IncomingLevel3[0].from, Index.get(), AST);
     EXPECT_THAT(IncomingLevel4, IsEmpty());
   };
 
@@ -616,7 +616,7 @@ TEST(CallHierarchy, CallInLocalVarDecl) {
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
 
-  auto Incoming = incomingCalls(Items[0], Index.get());
+  auto Incoming = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(Incoming, ElementsAre(AllOf(from(withName("caller1")),
                                           iFromRanges(Source.range("call1"))),
                                     AllOf(from(withName("caller2")),
@@ -643,7 +643,7 @@ TEST(CallHierarchy, HierarchyOnField) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("var1")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller")),
                                 iFromRanges(Source.range("Callee")))));
@@ -664,7 +664,7 @@ TEST(CallHierarchy, HierarchyOnVar) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("var")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller")),
                                 iFromRanges(Source.range("Callee")))));
@@ -686,14 +686,14 @@ TEST(CallHierarchy, HierarchyOnEnumConstant) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point("Heads"), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("heads")));
-  auto IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  auto IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller")),
                                 iFromRanges(Source.range("CallerH")))));
   Items =
       prepareCallHierarchy(AST, Source.point("Tails"), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("tails")));
-  IncomingLevel1 = incomingCalls(Items[0], Index.get());
+  IncomingLevel1 = incomingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(IncomingLevel1,
               ElementsAre(AllOf(from(withName("caller")),
                                 iFromRanges(Source.range("CallerT")))));
@@ -718,7 +718,7 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) {
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("callee")));
 
-  auto Incoming = incomingCalls(Items[0], Index.get());
+  auto Incoming = incomingCalls(Items[0], Index.get(), AST);
 
   // The only call site is in the source file, which is a different file from
   // the declaration of the function containing the call, which is in the

>From 47129963cf7b1dc5be3061bd9c3d0cca42d805db Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Sun, 14 Dec 2025 19:49:49 +0100
Subject: [PATCH 19/42] 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 be328a86d5177..78d8292eb8339 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1432,7 +1432,7 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
 void ClangdLSPServer::onCallHierarchyOutgoingCalls(
     const CallHierarchyOutgoingCallsParams &Params,
     Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
-  Server->outgoingCalls(Params.item, std::move(Reply));
+  Server->outgoingCalls(Params.item.uri.file(), Params.item, std::move(Reply));
 }
 
 void ClangdLSPServer::applyConfiguration(
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ab5fae7384c22..59d36de5c28fe 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -932,12 +932,16 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
 }
 
 void ClangdServer::outgoingCalls(
+    PathRef File,
     const CallHierarchyItem &Item,
     Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
-  WorkScheduler->run("Outgoing Calls", "",
-                     [CB = std::move(CB), Item, this]() mutable {
-                       CB(clangd::outgoingCalls(Item, Index));
-                     });
+  auto Action = [Item, CB = std::move(CB), this](
+      llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(clangd::outgoingCalls(Item, Index, InpAST->AST));
+  };
+  WorkScheduler->runWithAST("Outgoing Calls", File, std::move(Action));
 }
 
 void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 37ebed8905423..efe2ee42f791d 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -299,7 +299,7 @@ class ClangdServer {
                      Callback<std::vector<CallHierarchyIncomingCall>> CB);
 
   /// Resolve outgoing calls for a given call hierarchy item.
-  void outgoingCalls(const CallHierarchyItem &Item,
+  void outgoingCalls(PathRef File, const CallHierarchyItem &Item,
                      Callback<std::vector<CallHierarchyOutgoingCall>>);
 
   /// Resolve inlay hints for a given document.
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 3ae2be110e610..2862e2f409ae5 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -2415,27 +2415,21 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
     Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
       auto It = CallsIn.find(Caller.ID);
       assert(It != CallsIn.end());
+
+      std::optional<CallHierarchyItem> CHI;
       if (auto *ND = getNamedDeclFromSymbol(Caller, AST)) {
-        if (auto CHI = declToCallHierarchyItem(*ND, AST.tuPath())) {
-          std::vector<Range> FromRanges;
-          for (const Location &L : It->second) {
-            if (L.uri != CHI->uri) {
-              // Call location not in same file as caller.
-              // This can happen in some edge cases. There's not much we can do,
-              // since the protocol only allows returning ranges interpreted as
-              // being in the caller's file.
-              continue;
-            }
-            FromRanges.push_back(L.range);
-          }
-          Results.push_back(CallHierarchyIncomingCall{
-              std::move(*CHI), std::move(FromRanges), MightNeverCall});
-        }
-      } else if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
-        // Fallback to using symbol if NamedDecl is not available
+        CHI = declToCallHierarchyItem(*ND, AST.tuPath());
+      } else {
+        CHI = symbolToCallHierarchyItem(Caller, Item.uri.file());
+      }
+      if (CHI) {
         std::vector<Range> FromRanges;
         for (const Location &L : It->second) {
           if (L.uri != CHI->uri) {
+            // Call location not in same file as caller.
+            // This can happen in some edge cases. There's not much we can do,
+            // since the protocol only allows returning ranges interpreted as
+            // being in the caller's file.
             continue;
           }
           FromRanges.push_back(L.range);
@@ -2465,7 +2459,8 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
 }
 
 std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
+outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+              const ParsedAST &AST) {
   std::vector<CallHierarchyOutgoingCall> Results;
   if (!Index || Item.data.empty())
     return Results;
@@ -2511,7 +2506,16 @@ outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
 
     auto It = CallsOut.find(Callee.ID);
     assert(It != CallsOut.end());
-    if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file())) {
+
+    std::optional<CallHierarchyItem> CHI;
+
+    if (auto *ND = getNamedDeclFromSymbol(Callee, AST)) {
+      CHI = declToCallHierarchyItem(*ND, AST.tuPath());
+    } else {
+      CHI = symbolToCallHierarchyItem(Callee, Item.uri.file());
+    }
+
+    if (CHI) {
       std::vector<Range> FromRanges;
       for (const Location &L : It->second) {
         if (L.uri != Item.uri) {
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 1019fa189a613..6319729ba39e4 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -152,7 +152,8 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
               const ParsedAST &AST);
 
 std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
+outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index,
+              const ParsedAST &AST);
 
 /// Returns all decls that are referenced in the \p FD except local symbols.
 llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index f5e9983aa70ec..2891b420427d2 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -296,29 +296,29 @@ TEST(CallHierarchy, OutgoingOneFile) {
   std::vector<CallHierarchyItem> Items =
       prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
   ASSERT_THAT(Items, ElementsAre(withName("caller3")));
-  auto OugoingLevel1 = outgoingCalls(Items[0], Index.get());
+  auto OugoingLevel1 = outgoingCalls(Items[0], Index.get(), AST);
   ASSERT_THAT(
       OugoingLevel1,
       ElementsAre(
-          AllOf(to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
+          AllOf(to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))),
                 oFromRanges(Source.range("Caller1C"))),
           AllOf(to(AllOf(withName("caller2"), withDetail("caller2"))),
                 oFromRanges(Source.range("Caller2")))));
 
-  auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get());
+  auto OutgoingLevel2 = outgoingCalls(OugoingLevel1[1].to, Index.get(), AST);
   ASSERT_THAT(
       OutgoingLevel2,
       ElementsAre(AllOf(
-          to(AllOf(withName("caller1"), withDetail("ns::Foo::caller1"))),
+          to(AllOf(withName("Foo::caller1"), withDetail("ns::Foo::caller1"))),
           oFromRanges(Source.range("Caller1A"), Source.range("Caller1B")))));
 
-  auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
+  auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST);
   ASSERT_THAT(
       OutgoingLevel3,
       ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
                         oFromRanges(Source.range("Callee")))));
 
-  auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
+  auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST);
   EXPECT_THAT(OutgoingLevel4, IsEmpty());
 }
 
@@ -430,7 +430,7 @@ TEST(CallHierarchy, MultiFileCpp) {
         ElementsAre(AllOf(
             withName("caller3"),
             withFile(testPath(IsDeclaration ? "caller3.hh" : "caller3.cc")))));
-    auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get());
+    auto OutgoingLevel1 = outgoingCalls(Items[0], Index.get(), AST);
     ASSERT_THAT(
         OutgoingLevel1,
         // fromRanges are interpreted in the context of Items[0]'s file.
@@ -444,19 +444,19 @@ TEST(CallHierarchy, MultiFileCpp) {
                   IsDeclaration ? oFromRanges()
                                 : oFromRanges(Caller3C.range("Caller2")))));
 
-    auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get());
+    auto OutgoingLevel2 = outgoingCalls(OutgoingLevel1[1].to, Index.get(), AST);
     ASSERT_THAT(OutgoingLevel2,
                 ElementsAre(AllOf(
                     to(AllOf(withName("caller1"), withDetail("nsa::caller1"))),
                     oFromRanges(Caller2C.range("A"), Caller2C.range("B")))));
 
-    auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get());
+    auto OutgoingLevel3 = outgoingCalls(OutgoingLevel2[0].to, Index.get(), AST);
     ASSERT_THAT(
         OutgoingLevel3,
         ElementsAre(AllOf(to(AllOf(withName("callee"), withDetail("callee"))),
                           oFromRanges(Caller1C.range()))));
 
-    auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get());
+    auto OutgoingLevel4 = outgoingCalls(OutgoingLevel3[0].to, Index.get(), AST);
     EXPECT_THAT(OutgoingLevel4, IsEmpty());
   };
 

>From ec25102c6bc6fa5c39170f21e338fc6f50644d79 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 15 Dec 2025 11:02:01 +0100
Subject: [PATCH 20/42] 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 78d8292eb8339..df1984fe4c724 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1354,7 +1354,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));
 }
 
@@ -1374,7 +1374,7 @@ void ClangdLSPServer::onSuperTypes(
 void ClangdLSPServer::onSubTypes(
     const ResolveTypeHierarchyItemParams &Params,
     Callback<std::vector<TypeHierarchyItem>> Reply) {
-  Server->subTypes(Params.item, std::move(Reply));
+  Server->subTypes(Params.item.uri.file(), Params.item, std::move(Reply));
 }
 
 void ClangdLSPServer::onPrepareCallHierarchy(
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 59d36de5c28fe..3a0879085dc3b 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -879,21 +879,37 @@ void ClangdServer::superTypes(
                      });
 }
 
-void ClangdServer::subTypes(const TypeHierarchyItem &Item,
+void ClangdServer::subTypes(PathRef File, const TypeHierarchyItem &Item,
                             Callback<std::vector<TypeHierarchyItem>> CB) {
-  WorkScheduler->run(
-      "typeHierarchy/subTypes", /*Path=*/"",
-      [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
+
+  auto Action = [File = File.str(), Item, CB = std::move(CB), this](
+      llvm::Expected<InputsAndAST> InpAST) mutable {
+        if (!InpAST)
+            return CB(InpAST.takeError());
+        CB(clangd::subTypes(Item, Index, InpAST->AST));
+      };
+   WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
+  // WorkScheduler->run(
+  //     "typeHierarchy/subTypes", /*Path=*/"",
+  //     [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
 }
 
-void ClangdServer::resolveTypeHierarchy(
+void ClangdServer::resolveTypeHierarchy(PathRef File,
     TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
     Callback<std::optional<TypeHierarchyItem>> CB) {
-  WorkScheduler->run(
-      "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
-        clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
-        CB(Item);
-      });
+  auto Action = [=, CB = std::move(CB), this](
+      llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index, InpAST->AST);
+    CB(Item);
+  };
+  WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File, std::move(Action));
+  // WorkScheduler->run(
+  //     "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
+  //       clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
+  //       CB(Item);
+  //     });
 }
 
 void ClangdServer::prepareCallHierarchy(
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index efe2ee42f791d..5ec3db926f545 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -281,11 +281,11 @@ class ClangdServer {
   void superTypes(const TypeHierarchyItem &Item,
                   Callback<std::optional<std::vector<TypeHierarchyItem>>> CB);
   /// Get direct children of a type hierarchy item.
-  void subTypes(const TypeHierarchyItem &Item,
+  void subTypes(PathRef File, const TypeHierarchyItem &Item,
                 Callback<std::vector<TypeHierarchyItem>> CB);
 
   /// Resolve type hierarchy item in the given direction.
-  void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,
+  void resolveTypeHierarchy(PathRef File, TypeHierarchyItem Item, int Resolve,
                             TypeHierarchyDirection Direction,
                             Callback<std::optional<TypeHierarchyItem>> CB);
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 2862e2f409ae5..c1f53cc8e8f4f 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1863,18 +1863,58 @@ symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) {
   return Result;
 }
 
+// Tries to find a NamedDecl in the AST that matches the given Symbol.
+// Returns nullptr if the symbol is not found in the current AST.
+const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
+                                        const ParsedAST &AST) {
+  // Try to convert the symbol to a location and find the decl at that location
+  auto SymLoc = symbolToLocation(Sym, AST.tuPath());
+  if (!SymLoc)
+    return nullptr;
+
+  // Check if the symbol location is in the main file
+  if (SymLoc->uri.file() != AST.tuPath())
+    return nullptr;
+
+  // Convert LSP position to source location
+  const auto &SM = AST.getSourceManager();
+  auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
+  if (!CurLoc) {
+    llvm::consumeError(CurLoc.takeError());
+    return nullptr;
+  }
+
+  // Get all decls at this location
+  auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
+  if (Decls.empty())
+    return nullptr;
+
+  // Return the first decl (usually the most specific one)
+  return Decls[0];
+}
+
 static void fillSubTypes(const SymbolID &ID,
                          std::vector<TypeHierarchyItem> &SubTypes,
-                         const SymbolIndex *Index, int Levels, PathRef TUPath) {
+                         const SymbolIndex *Index, int Levels, PathRef TUPath,
+                         const ParsedAST &AST) {
   RelationsRequest Req;
   Req.Subjects.insert(ID);
   Req.Predicate = RelationKind::BaseOf;
   Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
-    if (std::optional<TypeHierarchyItem> ChildSym =
-            symbolToTypeHierarchyItem(Object, TUPath)) {
+    std::optional<TypeHierarchyItem> ChildSym;
+
+    if (auto *ND = getNamedDeclFromSymbol(Object, AST)) {
+      ChildSym = declToTypeHierarchyItem(*ND, AST.tuPath());
+      elog("fillSubTypes: declToTypeHierarchyItem, {0}", ChildSym.has_value());
+    } else {
+      ChildSym = symbolToTypeHierarchyItem(Object, TUPath);
+      elog("fillSubTypes: symbolToTypeHierarchyItem, {0}", ChildSym.has_value());
+    }
+    if (ChildSym) {
       if (Levels > 1) {
         ChildSym->children.emplace();
-        fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath);
+        fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath,
+                     AST);
       }
       SubTypes.emplace_back(std::move(*ChildSym));
     }
@@ -2257,7 +2297,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));
@@ -2289,9 +2329,9 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
 }
 
 std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
-                                        const SymbolIndex *Index) {
+                                        const SymbolIndex *Index, const ParsedAST &AST) {
   std::vector<TypeHierarchyItem> Results;
-  fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file());
+  fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST);
   for (auto &ChildSym : Results)
     ChildSym.data.parents = {Item.data};
   return Results;
@@ -2299,7 +2339,8 @@ std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
 
 void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                           TypeHierarchyDirection Direction,
-                          const SymbolIndex *Index) {
+                          const SymbolIndex *Index,
+                          const ParsedAST &AST) {
   // We only support typeHierarchy/resolve for children, because for parents
   // we ignore ResolveLevels and return all levels of parents eagerly.
   if (!Index || Direction == TypeHierarchyDirection::Parents ||
@@ -2308,37 +2349,7 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
 
   Item.children.emplace();
   fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels,
-               Item.uri.file());
-}
-
-// Tries to find a NamedDecl in the AST that matches the given Symbol.
-// Returns nullptr if the symbol is not found in the current AST.
-const NamedDecl *getNamedDeclFromSymbol(const Symbol &Sym,
-                                        const ParsedAST &AST) {
-  // Try to convert the symbol to a location and find the decl at that location
-  auto SymLoc = symbolToLocation(Sym, AST.tuPath());
-  if (!SymLoc)
-    return nullptr;
-
-  // Check if the symbol location is in the main file
-  if (SymLoc->uri.file() != AST.tuPath())
-    return nullptr;
-
-  // Convert LSP position to source location
-  const auto &SM = AST.getSourceManager();
-  auto CurLoc = sourceLocationInMainFile(SM, SymLoc->range.start);
-  if (!CurLoc) {
-    llvm::consumeError(CurLoc.takeError());
-    return nullptr;
-  }
-
-  // Get all decls at this location
-  auto Decls = getDeclAtPosition(const_cast<ParsedAST &>(AST), *CurLoc, {});
-  if (Decls.empty())
-    return nullptr;
-
-  // Return the first decl (usually the most specific one)
-  return Decls[0];
+               Item.uri.file(), AST);
 }
 
 std::vector<CallHierarchyItem>
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 6319729ba39e4..1582c481ea5c2 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -137,11 +137,12 @@ std::optional<std::vector<TypeHierarchyItem>>
 superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
 /// Returns direct children of a TypeHierarchyItem.
 std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
-                                        const SymbolIndex *Index);
+                                        const SymbolIndex *Index,
+                                        const ParsedAST &AST);
 
 void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                           TypeHierarchyDirection Direction,
-                          const SymbolIndex *Index);
+                          const SymbolIndex *Index, const ParsedAST &AST);
 
 /// Get call hierarchy information at \p Pos.
 std::vector<CallHierarchyItem>
diff --git a/clang-tools-extra/clangd/test/type-hierarchy-ext.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
index 983c7538088bf..d635b199d002c 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy-ext.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test
@@ -17,11 +17,11 @@
 # CHECK-NEXT:        "name": "Child3",
 # CHECK-NEXT:        "range": {
 # CHECK-NEXT:          "end": {
-# CHECK-NEXT:            "character": 13,
+# CHECK-NEXT:            "character": 25,
 # CHECK-NEXT:            "line": 3
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "start": {
-# CHECK-NEXT:            "character": 7,
+# CHECK-NEXT:            "character": 0,
 # CHECK-NEXT:            "line": 3
 # CHECK-NEXT:          }
 # CHECK-NEXT:        },
@@ -162,11 +162,11 @@
 # CHECK-NEXT:        "name": "Child4",
 # CHECK-NEXT:        "range": {
 # CHECK-NEXT:          "end": {
-# CHECK-NEXT:            "character": 13,
+# CHECK-NEXT:            "character": 25,
 # CHECK-NEXT:            "line": 4
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "start": {
-# CHECK-NEXT:            "character": 7,
+# CHECK-NEXT:            "character": 0,
 # CHECK-NEXT:            "line": 4
 # CHECK-NEXT:          }
 # CHECK-NEXT:        },
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index d1dda4b92c29c..065f129e05d2f 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -125,11 +125,11 @@
 # CHECK-NEXT:       "name": "Child3",
 # CHECK-NEXT:       "range": {
 # CHECK-NEXT:         "end": {
-# CHECK-NEXT:           "character": 13,
+# CHECK-NEXT:           "character": 25,
 # CHECK-NEXT:           "line": 3
 # CHECK-NEXT:         },
 # CHECK-NEXT:         "start": {
-# CHECK-NEXT:           "character": 7,
+# CHECK-NEXT:           "character": 0,
 # CHECK-NEXT:           "line": 3
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index 406a842f5a008..ae2f806d0b17e 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -756,7 +756,7 @@ struct Child2b : Child1 {};
                            parentsNotResolved(), childrenNotResolved()))));
 
   resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1,
-                       TypeHierarchyDirection::Children, Index.get());
+                       TypeHierarchyDirection::Children, Index.get(), AST);
 
   EXPECT_THAT(
       (*Result.front().children)[0],
@@ -783,7 +783,7 @@ struct Child : Parent1, Parent2 {};
                                  TypeHierarchyDirection::Children, Index.get(),
                                  testPath(TU.Filename));
   ASSERT_THAT(Result, SizeIs(1));
-  auto Children = subTypes(Result.front(), Index.get());
+  auto Children = subTypes(Result.front(), Index.get(), AST);
 
   // Make sure parents are populated when getting children.
   // FIXME: This is partial.

>From c61c43e2675462375f71836926cff0ae58175c5a Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 15 Dec 2025 16:25:26 +0100
Subject: [PATCH 21/42] 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/XRefs.cpp            | 47 ++++++++------
 clang-tools-extra/clangd/XRefs.h              |  6 +-
 .../clangd/test/type-hierarchy.test           |  4 +-
 .../clangd/unittests/TypeHierarchyTests.cpp   |  2 +-
 8 files changed, 99 insertions(+), 102 deletions(-)

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index df1984fe4c724..97240904c8936 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;
     }
   }
@@ -907,24 +906,24 @@ void ClangdLSPServer::onRename(const RenameParams &Params,
   if (!Server->getDraft(File))
     return Reply(llvm::make_error<LSPError>(
         "onRename called for non-added file", ErrorCode::InvalidParams));
-  Server->rename(File, Params.position, Params.newName, Opts.Rename,
-                 [File, Params, Reply = std::move(Reply),
-                  this](llvm::Expected<RenameResult> R) mutable {
-                   if (!R)
-                     return Reply(R.takeError());
-                   if (auto Err = validateEdits(*Server, R->GlobalChanges))
-                     return Reply(std::move(Err));
-                   WorkspaceEdit Result;
-                   // FIXME: use documentChanges if SupportDocumentChanges is
-                   // true.
-                   Result.changes.emplace();
-                   for (const auto &Rep : R->GlobalChanges) {
-                     (*Result
-                           .changes)[URI::createFile(Rep.first()).toString()] =
-                         Rep.second.asTextEdits();
-                   }
-                   Reply(Result);
-                 });
+  Server->rename(
+      File, Params.position, Params.newName, Opts.Rename,
+      [File, Params, Reply = std::move(Reply),
+       this](llvm::Expected<RenameResult> R) mutable {
+        if (!R)
+          return Reply(R.takeError());
+        if (auto Err = validateEdits(*Server, R->GlobalChanges))
+          return Reply(std::move(Err));
+        WorkspaceEdit Result;
+        // FIXME: use documentChanges if SupportDocumentChanges is
+        // true.
+        Result.changes.emplace();
+        for (const auto &Rep : R->GlobalChanges) {
+          (*Result.changes)[URI::createFile(Rep.first()).toString()] =
+              Rep.second.asTextEdits();
+        }
+        Reply(Result);
+      });
 }
 
 void ClangdLSPServer::onDocumentDidClose(
@@ -1068,7 +1067,7 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
   std::map<ClangdServer::DiagRef, clangd::Diagnostic> ToLSPDiags;
   ClangdServer::CodeActionInputs Inputs;
 
-  for (const auto& LSPDiag : Params.context.diagnostics) {
+  for (const auto &LSPDiag : Params.context.diagnostics) {
     if (auto DiagRef = getDiagRef(File.file(), LSPDiag)) {
       ToLSPDiags[*DiagRef] = LSPDiag;
       Inputs.Diagnostics.push_back(*DiagRef);
@@ -1077,13 +1076,9 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
   Inputs.File = File.file();
   Inputs.Selection = Params.range;
   Inputs.RequestedActionKinds = Params.context.only;
-  Inputs.TweakFilter = [this](const Tweak &T) {
-    return Opts.TweakFilter(T);
-  };
-  auto CB = [this,
-             Reply = std::move(Reply),
-             ToLSPDiags = std::move(ToLSPDiags), File,
-             Selection = Params.range](
+  Inputs.TweakFilter = [this](const Tweak &T) { return Opts.TweakFilter(T); };
+  auto CB = [this, Reply = std::move(Reply), ToLSPDiags = std::move(ToLSPDiags),
+             File, Selection = Params.range](
                 llvm::Expected<ClangdServer::CodeActionResult> Fixits) mutable {
     if (!Fixits)
       return Reply(Fixits.takeError());
@@ -1092,8 +1087,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};
       }
     }
@@ -1354,7 +1348,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));
 }
 
@@ -1368,7 +1363,7 @@ void ClangdLSPServer::onPrepareTypeHierarchy(
 void ClangdLSPServer::onSuperTypes(
     const ResolveTypeHierarchyItemParams &Params,
     Callback<std::optional<std::vector<TypeHierarchyItem>>> Reply) {
-  Server->superTypes(Params.item, std::move(Reply));
+  Server->superTypes(Params.item.uri.file(), Params.item, std::move(Reply));
 }
 
 void ClangdLSPServer::onSubTypes(
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6ada3fd9e6e47..64dcbfbc55325 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -133,7 +133,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
                   Callback<std::vector<Location>>);
   void onGoToImplementation(const TextDocumentPositionParams &,
                             Callback<std::vector<Location>>);
-  void onReference(const ReferenceParams &, Callback<std::vector<ReferenceLocation>>);
+  void onReference(const ReferenceParams &,
+                   Callback<std::vector<ReferenceLocation>>);
   void onSwitchSourceHeader(const TextDocumentIdentifier &,
                             Callback<std::optional<URIForFile>>);
   void onDocumentHighlight(const TextDocumentPositionParams &,
@@ -243,7 +244,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   /// Used to indicate the ClangdLSPServer is being destroyed.
   std::atomic<bool> IsBeingDestroyed = {false};
 
-  // FIXME: The caching is a temporary solution to get corresponding clangd 
+  // FIXME: The caching is a temporary solution to get corresponding clangd
   // diagnostic from a LSP diagnostic.
   // Ideally, ClangdServer can generate an identifier for each diagnostic,
   // emit them via the LSP's data field (which was newly added in LSP 3.16).
@@ -259,11 +260,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
     return {LSPDiag.range, LSPDiag.message};
   }
   /// A map from LSP diagnostic to clangd-naive diagnostic.
-  typedef std::map<DiagKey, ClangdServer::DiagRef>
-      DiagnosticToDiagRefMap;
+  typedef std::map<DiagKey, ClangdServer::DiagRef> DiagnosticToDiagRefMap;
   /// Caches the mapping LSP and clangd-naive diagnostics per file.
-  llvm::StringMap<DiagnosticToDiagRefMap>
-      DiagRefMap;
+  llvm::StringMap<DiagnosticToDiagRefMap> DiagRefMap;
 
   // Last semantic-tokens response, for incremental requests.
   std::mutex SemanticTokensMutex;
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 3a0879085dc3b..0828313f7aa13 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 {
@@ -871,45 +871,42 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
 }
 
 void ClangdServer::superTypes(
-    const TypeHierarchyItem &Item,
+    PathRef File, const TypeHierarchyItem &Item,
     Callback<std::optional<std::vector<TypeHierarchyItem>>> CB) {
-  WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",
-                     [=, CB = std::move(CB)]() mutable {
-                       CB(clangd::superTypes(Item, Index));
-                     });
+  auto Action = [File = File.str(), Item, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(clangd::superTypes(Item, Index, InpAST->AST));
+  };
+  WorkScheduler->runWithAST("superTypes Calls", File, std::move(Action));
 }
 
 void ClangdServer::subTypes(PathRef File, const TypeHierarchyItem &Item,
                             Callback<std::vector<TypeHierarchyItem>> CB) {
 
-  auto Action = [File = File.str(), Item, CB = std::move(CB), this](
-      llvm::Expected<InputsAndAST> InpAST) mutable {
-        if (!InpAST)
-            return CB(InpAST.takeError());
-        CB(clangd::subTypes(Item, Index, InpAST->AST));
-      };
-   WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
-  // WorkScheduler->run(
-  //     "typeHierarchy/subTypes", /*Path=*/"",
-  //     [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
+  auto Action = [File = File.str(), Item, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    CB(clangd::subTypes(Item, Index, InpAST->AST));
+  };
+  WorkScheduler->runWithAST("subTypes Calls", File, std::move(Action));
 }
 
-void ClangdServer::resolveTypeHierarchy(PathRef File,
-    TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
+void ClangdServer::resolveTypeHierarchy(
+    PathRef File, TypeHierarchyItem Item, int Resolve,
+    TypeHierarchyDirection Direction,
     Callback<std::optional<TypeHierarchyItem>> CB) {
-  auto Action = [=, CB = std::move(CB), this](
-      llvm::Expected<InputsAndAST> InpAST) mutable {
+  auto Action = [=, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());
     clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index, InpAST->AST);
     CB(Item);
   };
-  WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File, std::move(Action));
-  // WorkScheduler->run(
-  //     "Resolve Type Hierarchy", "", [=, CB = std::move(CB)]() mutable {
-  //       clangd::resolveTypeHierarchy(Item, Resolve, Direction, Index);
-  //       CB(Item);
-  //     });
+  WorkScheduler->runWithAST("resolveTypeHierarchy Calls", File,
+                            std::move(Action));
 }
 
 void ClangdServer::prepareCallHierarchy(
@@ -924,11 +921,10 @@ void ClangdServer::prepareCallHierarchy(
 }
 
 void ClangdServer::incomingCalls(
-    PathRef File,
-    const CallHierarchyItem &Item,
+    PathRef File, const CallHierarchyItem &Item,
     Callback<std::vector<CallHierarchyIncomingCall>> CB) {
-  auto Action = [Item, CB = std::move(CB), this](
-      llvm::Expected<InputsAndAST> InpAST) mutable {
+  auto Action = [Item, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());
     CB(clangd::incomingCalls(Item, Index, InpAST->AST));
@@ -948,11 +944,10 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
 }
 
 void ClangdServer::outgoingCalls(
-    PathRef File,
-    const CallHierarchyItem &Item,
+    PathRef File, const CallHierarchyItem &Item,
     Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
-  auto Action = [Item, CB = std::move(CB), this](
-      llvm::Expected<InputsAndAST> InpAST) mutable {
+  auto Action = [Item, CB = std::move(CB),
+                 this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());
     CB(clangd::outgoingCalls(Item, Index, InpAST->AST));
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 5ec3db926f545..19d43af0736a3 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -278,7 +278,7 @@ class ClangdServer {
                      TypeHierarchyDirection Direction,
                      Callback<std::vector<TypeHierarchyItem>> CB);
   /// Get direct parents of a type hierarchy item.
-  void superTypes(const TypeHierarchyItem &Item,
+  void superTypes(PathRef File, const TypeHierarchyItem &Item,
                   Callback<std::optional<std::vector<TypeHierarchyItem>>> CB);
   /// Get direct children of a type hierarchy item.
   void subTypes(PathRef File, const TypeHierarchyItem &Item,
@@ -294,8 +294,7 @@ class ClangdServer {
                             Callback<std::vector<CallHierarchyItem>> CB);
 
   /// Resolve incoming calls for a given call hierarchy item.
-  void incomingCalls(PathRef File,
-                     const CallHierarchyItem &Item,
+  void incomingCalls(PathRef File, const CallHierarchyItem &Item,
                      Callback<std::vector<CallHierarchyIncomingCall>> CB);
 
   /// Resolve outgoing calls for a given call hierarchy item.
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index c1f53cc8e8f4f..8534d9a1de2fa 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1900,15 +1900,14 @@ static void fillSubTypes(const SymbolID &ID,
   RelationsRequest Req;
   Req.Subjects.insert(ID);
   Req.Predicate = RelationKind::BaseOf;
-  Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) {
+  Index->relations(Req, [&Levels, &Index, &SubTypes, &TUPath,
+                         &AST](const SymbolID &Subject, const Symbol &Object) {
     std::optional<TypeHierarchyItem> ChildSym;
 
     if (auto *ND = getNamedDeclFromSymbol(Object, AST)) {
       ChildSym = declToTypeHierarchyItem(*ND, AST.tuPath());
-      elog("fillSubTypes: declToTypeHierarchyItem, {0}", ChildSym.has_value());
     } else {
       ChildSym = symbolToTypeHierarchyItem(Object, TUPath);
-      elog("fillSubTypes: symbolToTypeHierarchyItem, {0}", ChildSym.has_value());
     }
     if (ChildSym) {
       if (Levels > 1) {
@@ -2137,15 +2136,15 @@ static QualType typeForNode(const ASTContext &Ctx, const HeuristicResolver *H,
   return QualType();
 }
 
-// Given a type targeted by the cursor, return one or more types that are more interesting
-// to target.
-static void unwrapFindType(
-    QualType T, const HeuristicResolver* H, llvm::SmallVector<QualType>& Out) {
+// Given a type targeted by the cursor, return one or more types that are more
+// interesting to target.
+static void unwrapFindType(QualType T, const HeuristicResolver *H,
+                           llvm::SmallVector<QualType> &Out) {
   if (T.isNull())
     return;
 
   // If there's a specific type alias, point at that rather than unwrapping.
-  if (const auto* TDT = T->getAs<TypedefType>())
+  if (const auto *TDT = T->getAs<TypedefType>())
     return Out.push_back(QualType(TDT, 0));
 
   // Pointers etc => pointee type.
@@ -2179,8 +2178,8 @@ static void unwrapFindType(
 }
 
 // Convenience overload, to allow calling this without the out-parameter
-static llvm::SmallVector<QualType> unwrapFindType(
-    QualType T, const HeuristicResolver* H) {
+static llvm::SmallVector<QualType> unwrapFindType(QualType T,
+                                                  const HeuristicResolver *H) {
   llvm::SmallVector<QualType> Result;
   unwrapFindType(T, H, Result);
   return Result;
@@ -2202,9 +2201,9 @@ std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos,
     std::vector<LocatedSymbol> LocatedSymbols;
 
     // NOTE: unwrapFindType might return duplicates for something like
-    // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some
-    // information about the type you may have not known before
-    // (since unique_ptr<unique_ptr<T>> != unique_ptr<T>).
+    // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you
+    // some information about the type you may have not known before (since
+    // unique_ptr<unique_ptr<T>> != unique_ptr<T>).
     for (const QualType &Type : unwrapFindType(
              typeForNode(AST.getASTContext(), AST.getHeuristicResolver(), N),
              AST.getHeuristicResolver()))
@@ -2297,7 +2296,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));
@@ -2307,7 +2307,8 @@ getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
 }
 
 std::optional<std::vector<TypeHierarchyItem>>
-superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index,
+           const ParsedAST &AST) {
   std::vector<TypeHierarchyItem> Results;
   if (!Item.data.parents)
     return std::nullopt;
@@ -2319,8 +2320,14 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
     Req.IDs.insert(Parent.symbolID);
     IDToData[Parent.symbolID] = &Parent;
   }
-  Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) {
-    if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) {
+  Index->lookup(Req, [&Item, &Results, &IDToData, &AST](const Symbol &S) {
+    std::optional<TypeHierarchyItem> THI;
+    if (auto *ND = getNamedDeclFromSymbol(S, AST)) {
+      THI = declToTypeHierarchyItem(*ND, AST.tuPath());
+    } else {
+      THI = symbolToTypeHierarchyItem(S, Item.uri.file());
+    }
+    if (THI) {
       THI->data = *IDToData.lookup(S.ID);
       Results.emplace_back(std::move(*THI));
     }
@@ -2329,7 +2336,8 @@ superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
 }
 
 std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
-                                        const SymbolIndex *Index, const ParsedAST &AST) {
+                                        const SymbolIndex *Index,
+                                        const ParsedAST &AST) {
   std::vector<TypeHierarchyItem> Results;
   fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file(), AST);
   for (auto &ChildSym : Results)
@@ -2339,8 +2347,7 @@ std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
 
 void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
                           TypeHierarchyDirection Direction,
-                          const SymbolIndex *Index,
-                          const ParsedAST &AST) {
+                          const SymbolIndex *Index, const ParsedAST &AST) {
   // We only support typeHierarchy/resolve for children, because for parents
   // we ignore ResolveLevels and return all levels of parents eagerly.
   if (!Index || Direction == TypeHierarchyDirection::Parents ||
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 1582c481ea5c2..f2652b5f274db 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -132,9 +132,11 @@ std::vector<TypeHierarchyItem> getTypeHierarchy(
     const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
 
 /// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside
-/// the item.
+/// the item or using the AST.
 std::optional<std::vector<TypeHierarchyItem>>
-superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
+superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index,
+           const ParsedAST &AST);
+
 /// Returns direct children of a TypeHierarchyItem.
 std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
                                         const SymbolIndex *Index,
diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test
index 065f129e05d2f..1d04ed9a983d1 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -71,11 +71,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:       },
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 4117ba177cd26042552a1e63355592c95163692e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 9 Jan 2026 10:30:46 +0100
Subject: [PATCH 22/42] Reduced AST API, added unit-test introduced symbol-tag
 bitmask.

---
 clang-tools-extra/clangd/AST.cpp              | 137 ++++++------------
 clang-tools-extra/clangd/AST.h                |  45 +-----
 clang-tools-extra/clangd/FindSymbols.cpp      |  35 ++---
 .../clangd/SemanticHighlighting.cpp           |  96 +++++++++++-
 .../clangd/SemanticHighlighting.h             |  45 ++++++
 .../clangd/unittests/FindSymbolsTests.cpp     |  24 +++
 6 files changed, 229 insertions(+), 153 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index c1bc1651c1370..412b0a21b63a9 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -8,6 +8,7 @@
 
 #include "AST.h"
 
+#include "SemanticHighlighting.h"
 #include "SourceCode.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTTypeTraits.h"
@@ -193,100 +194,6 @@ bool isImplementationDetail(const Decl *D) {
                             D->getASTContext().getSourceManager());
 }
 
-// Whether T is const in a loose sense - is a variable with this type readonly?
-bool isConst(QualType T) {
-  if (T.isNull())
-    return false;
-  T = T.getNonReferenceType();
-  if (T.isConstQualified())
-    return true;
-  if (const auto *AT = T->getAsArrayTypeUnsafe())
-    return isConst(AT->getElementType());
-  if (isConst(T->getPointeeType()))
-    return true;
-  return false;
-}
-
-bool isConst(const Decl *D) {
-  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
-    return true;
-  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
-      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
-    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
-      return true;
-  }
-  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
-    if (OCPD->isReadOnly())
-      return true;
-  }
-  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
-    if (!MPD->hasSetter())
-      return true;
-  }
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
-    if (CMD->isConst())
-      return true;
-  }
-  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
-    return isConst(FD->getReturnType());
-  return false;
-}
-
-bool isStatic(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isStatic();
-  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
-    return VD->isStaticDataMember() || VD->isStaticLocal();
-  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
-    return OPD->isClassProperty();
-  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
-    return OMD->isClassMethod();
-  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
-    return FD->isStatic();
-  return false;
-}
-
-bool isAbstract(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isPureVirtual();
-  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
-    return CRD->hasDefinition() && CRD->isAbstract();
-  return false;
-}
-
-bool isVirtual(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isVirtual();
-  return false;
-}
-
-bool isFinal(const Decl *D) {
-  if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
-    return CRD->hasAttr<FinalAttr>();
-
-  if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
-    return CRD->hasAttr<FinalAttr>();
-
-  return false;
-}
-
-bool isUniqueDefinition(const NamedDecl *Decl) {
-  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
-    return Func->isThisDeclarationADefinition();
-  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
-    return Klass->isThisDeclarationADefinition();
-  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
-    return Iface->isThisDeclarationADefinition();
-  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
-    return Proto->isThisDeclarationADefinition();
-  if (auto *Var = dyn_cast<VarDecl>(Decl))
-    return Var->isThisDeclarationADefinition();
-  return isa<TemplateTypeParmDecl>(Decl) ||
-         isa<NonTypeTemplateParmDecl>(Decl) ||
-         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
-         isa<ObjCImplDecl>(Decl);
-}
-
 SourceLocation nameLocation(const clang::Decl &D, const SourceManager &SM) {
   auto L = D.getLocation();
   // For `- (void)foo` we want `foo` not the `-`.
@@ -1208,5 +1115,47 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD) {
   return Result;
 }
 
+SymbolTags computeSymbolTags(const NamedDecl &ND) {
+  SymbolTags result = 0;
+
+  if (ND.isDeprecated())
+    result |= 1 << static_cast<unsigned>(SymbolTag::Deprecated);
+
+  if (isConst(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::ReadOnly);
+
+  if (isStatic(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Static);
+
+  if (isVirtual(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Virtual);
+
+  if (isAbstract(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Abstract);
+
+  if (isFinal(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Final);
+
+  if (isUniqueDefinition(&ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Definition);
+  else if (!isa<UnresolvedUsingValueDecl>(ND))
+    result |= 1 << static_cast<unsigned>(SymbolTag::Declaration);
+
+  switch (ND.getAccess()) {
+  case AS_public:
+    result |= 1 << static_cast<unsigned>(SymbolTag::Public);
+    break;
+  case AS_protected:
+    result |= 1 << static_cast<unsigned>(SymbolTag::Protected);
+    break;
+  case AS_private:
+    result |= 1 << static_cast<unsigned>(SymbolTag::Private);
+    break;
+  default:
+    break;
+  }
+
+  return result;
+}
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 90e95c85085b0..45ff42c3391ac 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -34,6 +34,9 @@ class HeuristicResolver;
 
 namespace clangd {
 
+/// A bitmask type representing symbol tags.
+using SymbolTags = uint32_t;
+
 /// Returns true if the declaration is considered implementation detail based on
 /// heuristics. For example, a declaration whose name is not explicitly spelled
 /// in code is considered implementation detail.
@@ -153,45 +156,6 @@ bool isImplicitTemplateInstantiation(const NamedDecl *D);
 ///   explicit specialization.
 bool isExplicitTemplateSpecialization(const NamedDecl *D);
 
-// Whether T is const in a loose sense - is a variable with this type readonly?
-bool isConst(QualType T);
-
-// Whether D is const in a loose sense (should it be highlighted as such?)
-// FIXME: This is separate from whether *a particular usage* can mutate D.
-//        We may want V in V.size() to be readonly even if V is mutable.
-bool isConst(const Decl *D);
-
-// "Static" means many things in C++, only some get the "static" modifier.
-//
-// Meanings that do:
-// - Members associated with the class rather than the instance.
-//   This is what 'static' most often means across languages.
-// - static local variables
-//   These are similarly "detached from their context" by the static keyword.
-//   In practice, these are rarely used inside classes, reducing confusion.
-//
-// Meanings that don't:
-// - Namespace-scoped variables, which have static storage class.
-//   This is implicit, so the keyword "static" isn't so strongly associated.
-//   If we want a modifier for these, "global scope" is probably the concept.
-// - Namespace-scoped variables/functions explicitly marked "static".
-//   There the keyword changes *linkage* , which is a totally different concept.
-//   If we want to model this, "file scope" would be a nice modifier.
-//
-// This is confusing, and maybe we should use another name, but because "static"
-// is a standard LSP modifier, having one with that name has advantages.
-bool isStatic(const Decl *D);
-// Indicates whether declaration D is abstract in cases where D is a struct or a
-// class.
-bool isAbstract(const Decl *D);
-// Indicates whether declaration D is virtual in cases where D is a method.
-bool isVirtual(const Decl *D);
-// Indicates whether declaration D is final in cases where D is a struct, class
-// or method.
-bool isFinal(const Decl *D);
-// Indicates whether declaration D is a unique definition (as opposed to a
-// declaration).
-bool isUniqueDefinition(const NamedDecl *Decl);
 /// Returns a nested name specifier loc of \p ND if it was present in the
 /// source, e.g.
 ///     void ns::something::foo() -> returns 'ns::something'
@@ -300,6 +264,9 @@ bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT);
 SmallVector<const CXXConstructorDecl *, 1>
 searchConstructorsInForwardingFunction(const FunctionDecl *FD);
 
+/// Computes symbol tags for a given NamedDecl.
+SymbolTags computeSymbolTags(const NamedDecl &ND);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 28bee28634aeb..7412a58a16fdd 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -31,44 +31,41 @@ namespace clang {
 namespace clangd {
 
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
+  const auto SymbolTags = computeSymbolTags(ND);
   std::vector<SymbolTag> Tags;
 
-  if (ND.isDeprecated())
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Deprecated))
     Tags.push_back(SymbolTag::Deprecated);
 
-  if (isConst(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::ReadOnly))
     Tags.push_back(SymbolTag::ReadOnly);
 
-  if (isStatic(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Static))
     Tags.push_back(SymbolTag::Static);
 
-  if (isVirtual(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Virtual))
     Tags.push_back(SymbolTag::Virtual);
 
-  if (isAbstract(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Abstract))
     Tags.push_back(SymbolTag::Abstract);
 
-  if (isFinal(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Final))
     Tags.push_back(SymbolTag::Final);
 
-  if (isUniqueDefinition(&ND))
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Definition))
     Tags.push_back(SymbolTag::Definition);
-  else if (!isa<UnresolvedUsingValueDecl>(ND))
+
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Declaration))
     Tags.push_back(SymbolTag::Declaration);
 
-  switch (ND.getAccess()) {
-  case AS_public:
-    Tags.push_back(SymbolTag::Public);
-    break;
-  case AS_protected:
+   if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Public))
+     Tags.push_back(SymbolTag::Public);
+
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Protected))
     Tags.push_back(SymbolTag::Protected);
-    break;
-  case AS_private:
+
+  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Private))
     Tags.push_back(SymbolTag::Private);
-    break;
-  default:
-    break;
-  }
 
   return Tags;
 }
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index f53343951d7de..44fe71fb3e28e 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -7,7 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "SemanticHighlighting.h"
-#include "AST.h"
 #include "Config.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
@@ -1461,5 +1460,100 @@ std::vector<Range> getInactiveRegions(ParsedAST &AST) {
   return InactiveRegions;
 }
 
+
+// Whether T is const in a loose sense - is a variable with this type readonly?
+bool isConst(QualType T) {
+  if (T.isNull())
+    return false;
+  T = T.getNonReferenceType();
+  if (T.isConstQualified())
+    return true;
+  if (const auto *AT = T->getAsArrayTypeUnsafe())
+    return isConst(AT->getElementType());
+  if (isConst(T->getPointeeType()))
+    return true;
+  return false;
+}
+
+bool isConst(const Decl *D) {
+  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
+    return true;
+  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
+      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
+    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
+      return true;
+  }
+  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
+    if (OCPD->isReadOnly())
+      return true;
+  }
+  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
+    if (!MPD->hasSetter())
+      return true;
+  }
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
+    if (CMD->isConst())
+      return true;
+  }
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return isConst(FD->getReturnType());
+  return false;
+}
+
+bool isStatic(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isStatic();
+  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
+    return VD->isStaticDataMember() || VD->isStaticLocal();
+  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
+    return OPD->isClassProperty();
+  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
+    return OMD->isClassMethod();
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return FD->isStatic();
+  return false;
+}
+
+bool isAbstract(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isPureVirtual();
+  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasDefinition() && CRD->isAbstract();
+  return false;
+}
+
+bool isVirtual(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isVirtual();
+  return false;
+}
+
+bool isFinal(const Decl *D) {
+  if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
+
+  if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
+
+  return false;
+}
+
+bool isUniqueDefinition(const NamedDecl *Decl) {
+  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
+    return Func->isThisDeclarationADefinition();
+  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
+    return Klass->isThisDeclarationADefinition();
+  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
+    return Iface->isThisDeclarationADefinition();
+  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
+    return Proto->isThisDeclarationADefinition();
+  if (auto *Var = dyn_cast<VarDecl>(Decl))
+    return Var->isThisDeclarationADefinition();
+  return isa<TemplateTypeParmDecl>(Decl) ||
+         isa<NonTypeTemplateParmDecl>(Decl) ||
+         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
+         isa<ObjCImplDecl>(Decl);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index 59d742b83ee52..d4bae8bab8836 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -21,10 +21,14 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHTING_H
 
 #include "Protocol.h"
+
+#include "clang/AST/TypeBase.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/raw_ostream.h"
 
 namespace clang {
+class NamedDecl;
+
 namespace clangd {
 class ParsedAST;
 
@@ -126,6 +130,47 @@ std::vector<SemanticTokensEdit> diffTokens(llvm::ArrayRef<SemanticToken> Before,
 // are not included.
 std::vector<Range> getInactiveRegions(ParsedAST &AST);
 
+
+// Whether T is const in a loose sense - is a variable with this type readonly?
+bool isConst(QualType T);
+
+// Whether D is const in a loose sense (should it be highlighted as such?)
+// FIXME: This is separate from whether *a particular usage* can mutate D.
+//        We may want V in V.size() to be readonly even if V is mutable.
+bool isConst(const Decl *D);
+
+// "Static" means many things in C++, only some get the "static" modifier.
+//
+// Meanings that do:
+// - Members associated with the class rather than the instance.
+//   This is what 'static' most often means across languages.
+// - static local variables
+//   These are similarly "detached from their context" by the static keyword.
+//   In practice, these are rarely used inside classes, reducing confusion.
+//
+// Meanings that don't:
+// - Namespace-scoped variables, which have static storage class.
+//   This is implicit, so the keyword "static" isn't so strongly associated.
+//   If we want a modifier for these, "global scope" is probably the concept.
+// - Namespace-scoped variables/functions explicitly marked "static".
+//   There the keyword changes *linkage* , which is a totally different concept.
+//   If we want to model this, "file scope" would be a nice modifier.
+//
+// This is confusing, and maybe we should use another name, but because "static"
+// is a standard LSP modifier, having one with that name has advantages.
+bool isStatic(const Decl *D);
+// Indicates whether declaration D is abstract in cases where D is a struct or a
+// class.
+bool isAbstract(const Decl *D);
+// Indicates whether declaration D is virtual in cases where D is a method.
+bool isVirtual(const Decl *D);
+// Indicates whether declaration D is final in cases where D is a struct, class
+// or method.
+bool isFinal(const Decl *D);
+// Indicates whether declaration D is a unique definition (as opposed to a
+// declaration).
+bool isUniqueDefinition(const NamedDecl *Decl);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index 5b1630eb00cb1..cd9e4321aa56e 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -35,6 +35,8 @@ MATCHER_P(withName, N, "") { return arg.name == N; }
 MATCHER_P(withKind, Kind, "") { return arg.kind == Kind; }
 MATCHER_P(withDetail, Detail, "") { return arg.detail == Detail; }
 MATCHER_P(symRange, Range, "") { return arg.range == Range; }
+MATCHER_P(withSymbolTag, Tag, "") { return llvm::is_contained(arg.tags, Tag); }
+
 
 // GMock helpers for matching DocumentSymbol.
 MATCHER_P(symNameRange, Range, "") { return arg.selectionRange == Range; }
@@ -1131,6 +1133,28 @@ TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) {
                                    withName("Core"), withName("coreMethod")));
 }
 
+TEST(DocumentSymbolsTest, SymbolTags) {
+  TestTU TU;
+  Annotations Main(R"(
+      class A {
+        virtual void f() const = 0;
+      };
+    )");
+
+  TU.Code = Main.code().str();
+  auto Symbols = getSymbols(TU.build());
+  EXPECT_THAT(
+      Symbols,
+      ElementsAre(AllOf(
+          withName("A"), withSymbolTag(SymbolTag::Abstract),
+          withSymbolTag(SymbolTag::Definition),
+          children(AllOf(withName("f"), withSymbolTag(SymbolTag::ReadOnly),
+                         withSymbolTag(SymbolTag::Virtual),
+                         withSymbolTag(SymbolTag::Abstract),
+                         withSymbolTag(SymbolTag::Declaration),
+                         withSymbolTag(SymbolTag::Private))))));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang

>From e1810827de5f70e486fb8b3d7605d48c0482fe89 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 12 Jan 2026 09:49:33 +0100
Subject: [PATCH 23/42] Use parts of result of the function computeSymbolTags
 in SemanticHighlighting.

---
 clang-tools-extra/clangd/AST.cpp              | 29 ++++++++++++-------
 clang-tools-extra/clangd/FindSymbols.cpp      | 28 +++++++++++-------
 .../clangd/SemanticHighlighting.cpp           | 29 ++++++++++++++-----
 3 files changed, 57 insertions(+), 29 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 412b0a21b63a9..6d8e919e9536e 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -618,6 +618,10 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
 
   QualType DeducedType;
 };
+
+SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
+  return (1 << static_cast<unsigned>(ST));
+}
 } // namespace
 
 std::optional<QualType> getDeducedType(ASTContext &ASTCtx,
@@ -1119,37 +1123,40 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
   SymbolTags result = 0;
 
   if (ND.isDeprecated())
-    result |= 1 << static_cast<unsigned>(SymbolTag::Deprecated);
+    result |= toSymbolTagBitmask(SymbolTag::Deprecated);
 
   if (isConst(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::ReadOnly);
+    result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
 
   if (isStatic(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Static);
+    result |= toSymbolTagBitmask(SymbolTag::Static);
 
   if (isVirtual(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Virtual);
+    result |= toSymbolTagBitmask(SymbolTag::Virtual);
 
   if (isAbstract(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Abstract);
+    result |= toSymbolTagBitmask(SymbolTag::Abstract);
 
   if (isFinal(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Final);
+    result |= toSymbolTagBitmask(SymbolTag::Final);
 
+  // Do not treat an UnresolvedUsingValueDecl as a declaration.
+  // It's more common to think of it as a reference to the
+  // underlying declaration.
   if (isUniqueDefinition(&ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Definition);
+    result |= toSymbolTagBitmask(SymbolTag::Definition);
   else if (!isa<UnresolvedUsingValueDecl>(ND))
-    result |= 1 << static_cast<unsigned>(SymbolTag::Declaration);
+    result |= toSymbolTagBitmask(SymbolTag::Declaration);
 
   switch (ND.getAccess()) {
   case AS_public:
-    result |= 1 << static_cast<unsigned>(SymbolTag::Public);
+    result |= toSymbolTagBitmask(SymbolTag::Public);
     break;
   case AS_protected:
-    result |= 1 << static_cast<unsigned>(SymbolTag::Protected);
+    result |= toSymbolTagBitmask(SymbolTag::Protected);
     break;
   case AS_private:
-    result |= 1 << static_cast<unsigned>(SymbolTag::Private);
+    result |= toSymbolTagBitmask(SymbolTag::Private);
     break;
   default:
     break;
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 7412a58a16fdd..077accb7d2ee7 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -30,41 +30,47 @@
 namespace clang {
 namespace clangd {
 
+namespace {
+SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
+  return (1 << static_cast<unsigned>(ST));
+}
+}
+
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   const auto SymbolTags = computeSymbolTags(ND);
   std::vector<SymbolTag> Tags;
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Deprecated))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
     Tags.push_back(SymbolTag::Deprecated);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::ReadOnly))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::ReadOnly))
     Tags.push_back(SymbolTag::ReadOnly);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Static))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Static))
     Tags.push_back(SymbolTag::Static);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Virtual))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Virtual))
     Tags.push_back(SymbolTag::Virtual);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Abstract))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Abstract))
     Tags.push_back(SymbolTag::Abstract);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Final))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Final))
     Tags.push_back(SymbolTag::Final);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Definition))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
     Tags.push_back(SymbolTag::Definition);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Declaration))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
     Tags.push_back(SymbolTag::Declaration);
 
-   if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Public))
+   if (SymbolTags & toSymbolTagBitmask(SymbolTag::Public))
      Tags.push_back(SymbolTag::Public);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Protected))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Protected))
     Tags.push_back(SymbolTag::Protected);
 
-  if (SymbolTags & 1 << static_cast<unsigned>(SymbolTag::Private))
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Private))
     Tags.push_back(SymbolTag::Private);
 
   return Tags;
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 44fe71fb3e28e..0a5f81fe3b029 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -32,6 +32,8 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
+
+#include <AST.h>
 #include <algorithm>
 #include <optional>
 
@@ -1024,6 +1026,10 @@ class CollectExtraHighlightings
 private:
   HighlightingsBuilder &H;
 };
+
+SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
+  return (1 << static_cast<unsigned>(ST));
+}
 } // namespace
 
 std::vector<HighlightingToken>
@@ -1055,22 +1061,31 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
           }
           if (auto Mod = scopeModifier(Decl))
             Tok.addModifier(*Mod);
-          if (isConst(Decl))
+
+          const auto SymbolTags = computeSymbolTags(*Decl);
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
+            Tok.addModifier(HighlightingModifier::Deprecated);
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::ReadOnly))
             Tok.addModifier(HighlightingModifier::Readonly);
-          if (isStatic(Decl))
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Static))
             Tok.addModifier(HighlightingModifier::Static);
-          if (isAbstract(Decl))
-            Tok.addModifier(HighlightingModifier::Abstract);
-          if (isVirtual(Decl))
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Virtual))
             Tok.addModifier(HighlightingModifier::Virtual);
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Abstract))
+            Tok.addModifier(HighlightingModifier::Abstract);
+
           if (isDependent(Decl))
             Tok.addModifier(HighlightingModifier::DependentName);
           if (isDefaultLibrary(Decl))
             Tok.addModifier(HighlightingModifier::DefaultLibrary);
-          if (Decl->isDeprecated())
-            Tok.addModifier(HighlightingModifier::Deprecated);
           if (isa<CXXConstructorDecl>(Decl))
             Tok.addModifier(HighlightingModifier::ConstructorOrDestructor);
+
           if (R.IsDecl) {
             // Do not treat an UnresolvedUsingValueDecl as a declaration.
             // It's more common to think of it as a reference to the

>From 6f0deffa9b9c1dbe983928e2a1e18ebe3ba5ee49 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 09:07:51 +0100
Subject: [PATCH 24/42] Overload computeSymbolTags to encapsulate the proper
 determination of Declarations and Definitions.

---
 clang-tools-extra/clangd/AST.cpp              | 45 +++++++++++--------
 clang-tools-extra/clangd/AST.h                |  4 ++
 clang-tools-extra/clangd/FindSymbols.cpp      |  6 +--
 .../clangd/SemanticHighlighting.cpp           | 19 ++++----
 .../clangd/test/symbol-tags.test              |  9 +++-
 clang-tools-extra/clangd/test/symbols.test    |  3 +-
 6 files changed, 53 insertions(+), 33 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 6d8e919e9536e..7c7b1c0f437c9 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -1119,50 +1119,59 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD) {
   return Result;
 }
 
+// Backwards-compatible default behavior: determine whether this NamedDecl is
+// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
 SymbolTags computeSymbolTags(const NamedDecl &ND) {
-  SymbolTags result = 0;
+  const auto IsDef = isUniqueDefinition(&ND);
+  return computeSymbolTags(ND, true, IsDef);
+}
+
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef) {
+  SymbolTags Result = 0;
 
   if (ND.isDeprecated())
-    result |= toSymbolTagBitmask(SymbolTag::Deprecated);
+    Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
 
   if (isConst(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
+    Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
 
   if (isStatic(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Static);
+    Result |= toSymbolTagBitmask(SymbolTag::Static);
 
   if (isVirtual(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Virtual);
+    Result |= toSymbolTagBitmask(SymbolTag::Virtual);
 
   if (isAbstract(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Abstract);
+    Result |= toSymbolTagBitmask(SymbolTag::Abstract);
 
   if (isFinal(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Final);
+    Result |= toSymbolTagBitmask(SymbolTag::Final);
 
-  // Do not treat an UnresolvedUsingValueDecl as a declaration.
-  // It's more common to think of it as a reference to the
-  // underlying declaration.
-  if (isUniqueDefinition(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Definition);
-  else if (!isa<UnresolvedUsingValueDecl>(ND))
-    result |= toSymbolTagBitmask(SymbolTag::Declaration);
+  if (IsDecl && 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
+    // underlying declaration.
+    Result |= toSymbolTagBitmask(SymbolTag::Declaration);
+
+    if (IsDef)
+      Result |= toSymbolTagBitmask(SymbolTag::Definition);
+  }
 
   switch (ND.getAccess()) {
   case AS_public:
-    result |= toSymbolTagBitmask(SymbolTag::Public);
+    Result |= toSymbolTagBitmask(SymbolTag::Public);
     break;
   case AS_protected:
-    result |= toSymbolTagBitmask(SymbolTag::Protected);
+    Result |= toSymbolTagBitmask(SymbolTag::Protected);
     break;
   case AS_private:
-    result |= toSymbolTagBitmask(SymbolTag::Private);
+    Result |= toSymbolTagBitmask(SymbolTag::Private);
     break;
   default:
     break;
   }
 
-  return result;
+  return Result;
 }
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 45ff42c3391ac..53270126cf348 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -267,6 +267,10 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD);
 /// Computes symbol tags for a given NamedDecl.
 SymbolTags computeSymbolTags(const NamedDecl &ND);
 
+/// Computes symbol tags for a given NamedDecl, with additional context about
+/// whether it's a declaration or definition.
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 077accb7d2ee7..693f3f59116cf 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -34,7 +34,7 @@ namespace {
 SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
   return (1 << static_cast<unsigned>(ST));
 }
-}
+} // namespace
 
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   const auto SymbolTags = computeSymbolTags(ND);
@@ -64,8 +64,8 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
   if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
     Tags.push_back(SymbolTag::Declaration);
 
-   if (SymbolTags & toSymbolTagBitmask(SymbolTag::Public))
-     Tags.push_back(SymbolTag::Public);
+  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Public))
+    Tags.push_back(SymbolTag::Public);
 
   if (SymbolTags & toSymbolTagBitmask(SymbolTag::Protected))
     Tags.push_back(SymbolTag::Protected);
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 0a5f81fe3b029..4865f44993f61 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -10,6 +10,7 @@
 #include "Config.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
+#include "AST.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "support/Logger.h"
@@ -1062,7 +1063,7 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
           if (auto Mod = scopeModifier(Decl))
             Tok.addModifier(*Mod);
 
-          const auto SymbolTags = computeSymbolTags(*Decl);
+          const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl, isUniqueDefinition(Decl));
 
           if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
             Tok.addModifier(HighlightingModifier::Deprecated);
@@ -1081,20 +1082,18 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
 
           if (isDependent(Decl))
             Tok.addModifier(HighlightingModifier::DependentName);
+
           if (isDefaultLibrary(Decl))
             Tok.addModifier(HighlightingModifier::DefaultLibrary);
+
           if (isa<CXXConstructorDecl>(Decl))
             Tok.addModifier(HighlightingModifier::ConstructorOrDestructor);
 
-          if (R.IsDecl) {
-            // Do not treat an UnresolvedUsingValueDecl as a declaration.
-            // It's more common to think of it as a reference to the
-            // underlying declaration.
-            if (!isa<UnresolvedUsingValueDecl>(Decl))
-              Tok.addModifier(HighlightingModifier::Declaration);
-            if (isUniqueDefinition(Decl))
-              Tok.addModifier(HighlightingModifier::Definition);
-          }
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
+            Tok.addModifier(HighlightingModifier::Declaration);
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
+            Tok.addModifier(HighlightingModifier::Definition);
         }
       },
       AST.getHeuristicResolver());
diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index 3d4821ae32081..b59c5181cce9b 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -40,6 +40,7 @@
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            15,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            5
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
@@ -130,6 +131,7 @@
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            4
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
@@ -160,6 +162,7 @@
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            8,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            2
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
@@ -189,7 +192,8 @@
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        9,
-# CHECK-NEXT:        19
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
 # CHECK-NEXT:    {
@@ -219,6 +223,7 @@
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        20,
 # CHECK-NEXT:        19,
+# CHECK-NEXT:        18,
 # CHECK-NEXT:        5
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
@@ -252,6 +257,7 @@
 # CHECK-NEXT:            15,
 # CHECK-NEXT:            10,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            5
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
@@ -282,6 +288,7 @@
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        10,
 # CHECK-NEXT:        19
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    }
 # CHECK-NEXT:  ]
diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index 40115f2496e50..4d818d9d1dcf4 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -86,7 +86,8 @@
 # CHECK-NEXT:          }
 # CHECK-NEXT:        },
 # CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        19
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:      }
 # CHECK-NEXT:    ]

>From ea5fd1c8887428058b07f76df3dcf952293cfa2e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 12:26:14 +0100
Subject: [PATCH 25/42] Relocated computeSymbolTags to the module FindSymbols.
 Refactoring on getSymbolTags and computeSymbolTags. Improved SymbolTags test.

---
 clang-tools-extra/clangd/AST.cpp              |  57 -----
 clang-tools-extra/clangd/AST.h                |  10 -
 clang-tools-extra/clangd/FindSymbols.cpp      | 209 +++++++++++++++---
 clang-tools-extra/clangd/FindSymbols.h        |  11 +
 .../clangd/SemanticHighlighting.cpp           |  97 +-------
 .../clangd/SemanticHighlighting.h             |  37 ----
 .../clangd/unittests/FindSymbolsTests.cpp     |  22 +-
 7 files changed, 210 insertions(+), 233 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 7c7b1c0f437c9..3f96faba18cf3 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -619,9 +619,6 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
   QualType DeducedType;
 };
 
-SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
-  return (1 << static_cast<unsigned>(ST));
-}
 } // namespace
 
 std::optional<QualType> getDeducedType(ASTContext &ASTCtx,
@@ -1119,59 +1116,5 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD) {
   return Result;
 }
 
-// Backwards-compatible default behavior: determine whether this NamedDecl is
-// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
-SymbolTags computeSymbolTags(const NamedDecl &ND) {
-  const auto IsDef = isUniqueDefinition(&ND);
-  return computeSymbolTags(ND, true, IsDef);
-}
-
-SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef) {
-  SymbolTags Result = 0;
-
-  if (ND.isDeprecated())
-    Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
-
-  if (isConst(&ND))
-    Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
-
-  if (isStatic(&ND))
-    Result |= toSymbolTagBitmask(SymbolTag::Static);
-
-  if (isVirtual(&ND))
-    Result |= toSymbolTagBitmask(SymbolTag::Virtual);
-
-  if (isAbstract(&ND))
-    Result |= toSymbolTagBitmask(SymbolTag::Abstract);
-
-  if (isFinal(&ND))
-    Result |= toSymbolTagBitmask(SymbolTag::Final);
-
-  if (IsDecl && 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
-    // underlying declaration.
-    Result |= toSymbolTagBitmask(SymbolTag::Declaration);
-
-    if (IsDef)
-      Result |= toSymbolTagBitmask(SymbolTag::Definition);
-  }
-
-  switch (ND.getAccess()) {
-  case AS_public:
-    Result |= toSymbolTagBitmask(SymbolTag::Public);
-    break;
-  case AS_protected:
-    Result |= toSymbolTagBitmask(SymbolTag::Protected);
-    break;
-  case AS_private:
-    Result |= toSymbolTagBitmask(SymbolTag::Private);
-    break;
-  default:
-    break;
-  }
-
-  return Result;
-}
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 53270126cf348..2bb4943b6de0b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -34,9 +34,6 @@ class HeuristicResolver;
 
 namespace clangd {
 
-/// A bitmask type representing symbol tags.
-using SymbolTags = uint32_t;
-
 /// Returns true if the declaration is considered implementation detail based on
 /// heuristics. For example, a declaration whose name is not explicitly spelled
 /// in code is considered implementation detail.
@@ -264,13 +261,6 @@ bool isLikelyForwardingFunction(const FunctionTemplateDecl *FT);
 SmallVector<const CXXConstructorDecl *, 1>
 searchConstructorsInForwardingFunction(const FunctionDecl *FD);
 
-/// Computes symbol tags for a given NamedDecl.
-SymbolTags computeSymbolTags(const NamedDecl &ND);
-
-/// Computes symbol tags for a given NamedDecl, with additional context about
-/// whether it's a declaration or definition.
-SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef);
-
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 693f3f59116cf..bfb6ca3462dcd 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -36,43 +36,202 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
 }
 } // namespace
 
-std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
-  const auto SymbolTags = computeSymbolTags(ND);
-  std::vector<SymbolTag> Tags;
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
-    Tags.push_back(SymbolTag::Deprecated);
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::ReadOnly))
-    Tags.push_back(SymbolTag::ReadOnly);
+// "Static" means many things in C++, only some get the "static" modifier.
+//
+// Meanings that do:
+// - Members associated with the class rather than the instance.
+//   This is what 'static' most often means across languages.
+// - static local variables
+//   These are similarly "detached from their context" by the static keyword.
+//   In practice, these are rarely used inside classes, reducing confusion.
+//
+// Meanings that don't:
+// - Namespace-scoped variables, which have static storage class.
+//   This is implicit, so the keyword "static" isn't so strongly associated.
+//   If we want a modifier for these, "global scope" is probably the concept.
+// - Namespace-scoped variables/functions explicitly marked "static".
+//   There the keyword changes *linkage* , which is a totally different concept.
+//   If we want to model this, "file scope" would be a nice modifier.
+//
+// This is confusing, and maybe we should use another name, but because "static"
+// is a standard LSP modifier, having one with that name has advantages.
+bool isStatic(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isStatic();
+  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
+    return VD->isStaticDataMember() || VD->isStaticLocal();
+  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
+    return OPD->isClassProperty();
+  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
+    return OMD->isClassMethod();
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return FD->isStatic();
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Static))
-    Tags.push_back(SymbolTag::Static);
+// Whether T is const in a loose sense - is a variable with this type readonly?
+bool isConst(QualType T) {
+  if (T.isNull())
+    return false;
+  T = T.getNonReferenceType();
+  if (T.isConstQualified())
+    return true;
+  if (const auto *AT = T->getAsArrayTypeUnsafe())
+    return isConst(AT->getElementType());
+  if (isConst(T->getPointeeType()))
+    return true;
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Virtual))
-    Tags.push_back(SymbolTag::Virtual);
+// Whether D is const in a loose sense (should it be highlighted as such?)
+// FIXME: This is separate from whether *a particular usage* can mutate D.
+//        We may want V in V.size() to be readonly even if V is mutable.
+bool isConst(const Decl *D) {
+  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
+    return true;
+  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
+      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
+    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
+      return true;
+      }
+  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
+    if (OCPD->isReadOnly())
+      return true;
+  }
+  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
+    if (!MPD->hasSetter())
+      return true;
+  }
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
+    if (CMD->isConst())
+      return true;
+  }
+  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+    return isConst(FD->getReturnType());
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Abstract))
-    Tags.push_back(SymbolTag::Abstract);
+// Indicates whether declaration D is abstract in cases where D is a struct or a
+// class.
+bool isAbstract(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isPureVirtual();
+  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasDefinition() && CRD->isAbstract();
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Final))
-    Tags.push_back(SymbolTag::Final);
+// Indicates whether declaration D is virtual in cases where D is a method.
+bool isVirtual(const Decl *D) {
+  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+    return CMD->isVirtual();
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
-    Tags.push_back(SymbolTag::Definition);
+// Indicates whether declaration D is final in cases where D is a struct, class
+// or method.
+bool isFinal(const Decl *D) {
+  if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
-    Tags.push_back(SymbolTag::Declaration);
+  if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
+    return CRD->hasAttr<FinalAttr>();
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Public))
-    Tags.push_back(SymbolTag::Public);
+  return false;
+}
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Protected))
-    Tags.push_back(SymbolTag::Protected);
 
-  if (SymbolTags & toSymbolTagBitmask(SymbolTag::Private))
-    Tags.push_back(SymbolTag::Private);
+bool isUniqueDefinition(const NamedDecl *Decl) {
+  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
+    return Func->isThisDeclarationADefinition();
+  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
+    return Klass->isThisDeclarationADefinition();
+  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
+    return Iface->isThisDeclarationADefinition();
+  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
+    return Proto->isThisDeclarationADefinition();
+  if (auto *Var = dyn_cast<VarDecl>(Decl))
+    return Var->isThisDeclarationADefinition();
+  return isa<TemplateTypeParmDecl>(Decl) ||
+         isa<NonTypeTemplateParmDecl>(Decl) ||
+         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
+         isa<ObjCImplDecl>(Decl);
+}
+
+// Backwards-compatible default behavior: determine whether this NamedDecl is
+// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
+SymbolTags computeSymbolTags(const NamedDecl &ND) {
+  return computeSymbolTags(ND, true);
+}
+
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl) {
+  SymbolTags Result = 0;
+  const auto IsDef = isUniqueDefinition(&ND);
 
+  if (ND.isDeprecated())
+    Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
+
+  if (isConst(&ND))
+    Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
+
+  if (isStatic(&ND))
+    Result |= toSymbolTagBitmask(SymbolTag::Static);
+
+  if (isVirtual(&ND))
+    Result |= toSymbolTagBitmask(SymbolTag::Virtual);
+
+  if (isAbstract(&ND))
+    Result |= toSymbolTagBitmask(SymbolTag::Abstract);
+
+  if (isFinal(&ND))
+    Result |= toSymbolTagBitmask(SymbolTag::Final);
+
+  if (IsDecl && 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
+    // underlying declaration.
+    Result |= toSymbolTagBitmask(SymbolTag::Declaration);
+
+    if (IsDef)
+      Result |= toSymbolTagBitmask(SymbolTag::Definition);
+  }
+
+  switch (ND.getAccess()) {
+  case AS_public:
+    Result |= toSymbolTagBitmask(SymbolTag::Public);
+    break;
+  case AS_protected:
+    Result |= toSymbolTagBitmask(SymbolTag::Protected);
+    break;
+  case AS_private:
+    Result |= toSymbolTagBitmask(SymbolTag::Private);
+    break;
+  default:
+    break;
+  }
+
+  return Result;
+}
+
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
+  const auto symbolTags = computeSymbolTags(ND);
+  std::vector<SymbolTag> Tags;
+
+  if (symbolTags == 0)
+    return Tags;
+
+  // Iterate through SymbolTag enum values and collect any that are present in
+  // the bitmask. SymbolTag values are in the numeric range
+  // [Deprecated .. ReadOnly].
+  constexpr unsigned MinTag = static_cast<unsigned>(SymbolTag::Deprecated);
+  constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::ReadOnly);
+  for (unsigned I = MinTag; I <= MaxTag; ++I) {
+    auto ST = static_cast<SymbolTag>(I);
+    if (symbolTags & toSymbolTagBitmask(ST))
+      Tags.push_back(ST);
+  }
   return Tags;
 }
 
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 075bf09f1a75b..81683cf13d8a6 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -22,6 +22,9 @@ namespace clangd {
 class ParsedAST;
 class SymbolIndex;
 
+/// A bitmask type representing symbol tags.
+using SymbolTags = uint32_t;
+
 /// Helper function for deriving an LSP Location from an index SymbolLocation.
 llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,
                                             llvm::StringRef TUPath);
@@ -52,6 +55,14 @@ llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 /// \p ND The declaration to get tags for.
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
 
+
+/// Computes symbol tags for a given NamedDecl.
+SymbolTags computeSymbolTags(const NamedDecl &ND);
+
+/// Computes symbol tags for a given NamedDecl, with additional context about
+/// whether it's a declaration or definition.
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 4865f44993f61..54e19bc7146be 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -8,6 +8,7 @@
 
 #include "SemanticHighlighting.h"
 #include "Config.h"
+#include "FindSymbols.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
 #include "AST.h"
@@ -1063,7 +1064,7 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
           if (auto Mod = scopeModifier(Decl))
             Tok.addModifier(*Mod);
 
-          const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl, isUniqueDefinition(Decl));
+          const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl);
 
           if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
             Tok.addModifier(HighlightingModifier::Deprecated);
@@ -1475,99 +1476,5 @@ std::vector<Range> getInactiveRegions(ParsedAST &AST) {
 }
 
 
-// Whether T is const in a loose sense - is a variable with this type readonly?
-bool isConst(QualType T) {
-  if (T.isNull())
-    return false;
-  T = T.getNonReferenceType();
-  if (T.isConstQualified())
-    return true;
-  if (const auto *AT = T->getAsArrayTypeUnsafe())
-    return isConst(AT->getElementType());
-  if (isConst(T->getPointeeType()))
-    return true;
-  return false;
-}
-
-bool isConst(const Decl *D) {
-  if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
-    return true;
-  if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
-      llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
-    if (isConst(llvm::cast<ValueDecl>(D)->getType()))
-      return true;
-  }
-  if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
-    if (OCPD->isReadOnly())
-      return true;
-  }
-  if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
-    if (!MPD->hasSetter())
-      return true;
-  }
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
-    if (CMD->isConst())
-      return true;
-  }
-  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
-    return isConst(FD->getReturnType());
-  return false;
-}
-
-bool isStatic(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isStatic();
-  if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
-    return VD->isStaticDataMember() || VD->isStaticLocal();
-  if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
-    return OPD->isClassProperty();
-  if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
-    return OMD->isClassMethod();
-  if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
-    return FD->isStatic();
-  return false;
-}
-
-bool isAbstract(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isPureVirtual();
-  if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
-    return CRD->hasDefinition() && CRD->isAbstract();
-  return false;
-}
-
-bool isVirtual(const Decl *D) {
-  if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
-    return CMD->isVirtual();
-  return false;
-}
-
-bool isFinal(const Decl *D) {
-  if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
-    return CRD->hasAttr<FinalAttr>();
-
-  if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
-    return CRD->hasAttr<FinalAttr>();
-
-  return false;
-}
-
-bool isUniqueDefinition(const NamedDecl *Decl) {
-  if (auto *Func = dyn_cast<FunctionDecl>(Decl))
-    return Func->isThisDeclarationADefinition();
-  if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
-    return Klass->isThisDeclarationADefinition();
-  if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
-    return Iface->isThisDeclarationADefinition();
-  if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
-    return Proto->isThisDeclarationADefinition();
-  if (auto *Var = dyn_cast<VarDecl>(Decl))
-    return Var->isThisDeclarationADefinition();
-  return isa<TemplateTypeParmDecl>(Decl) ||
-         isa<NonTypeTemplateParmDecl>(Decl) ||
-         isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
-         isa<ObjCImplDecl>(Decl);
-}
-
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index d4bae8bab8836..7ba8f423308fb 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -130,43 +130,6 @@ std::vector<SemanticTokensEdit> diffTokens(llvm::ArrayRef<SemanticToken> Before,
 // are not included.
 std::vector<Range> getInactiveRegions(ParsedAST &AST);
 
-
-// Whether T is const in a loose sense - is a variable with this type readonly?
-bool isConst(QualType T);
-
-// Whether D is const in a loose sense (should it be highlighted as such?)
-// FIXME: This is separate from whether *a particular usage* can mutate D.
-//        We may want V in V.size() to be readonly even if V is mutable.
-bool isConst(const Decl *D);
-
-// "Static" means many things in C++, only some get the "static" modifier.
-//
-// Meanings that do:
-// - Members associated with the class rather than the instance.
-//   This is what 'static' most often means across languages.
-// - static local variables
-//   These are similarly "detached from their context" by the static keyword.
-//   In practice, these are rarely used inside classes, reducing confusion.
-//
-// Meanings that don't:
-// - Namespace-scoped variables, which have static storage class.
-//   This is implicit, so the keyword "static" isn't so strongly associated.
-//   If we want a modifier for these, "global scope" is probably the concept.
-// - Namespace-scoped variables/functions explicitly marked "static".
-//   There the keyword changes *linkage* , which is a totally different concept.
-//   If we want to model this, "file scope" would be a nice modifier.
-//
-// This is confusing, and maybe we should use another name, but because "static"
-// is a standard LSP modifier, having one with that name has advantages.
-bool isStatic(const Decl *D);
-// Indicates whether declaration D is abstract in cases where D is a struct or a
-// class.
-bool isAbstract(const Decl *D);
-// Indicates whether declaration D is virtual in cases where D is a method.
-bool isVirtual(const Decl *D);
-// Indicates whether declaration D is final in cases where D is a struct, class
-// or method.
-bool isFinal(const Decl *D);
 // Indicates whether declaration D is a unique definition (as opposed to a
 // declaration).
 bool isUniqueDefinition(const NamedDecl *Decl);
diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index 8b61bdaebadc9..b0cd6a8368982 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -35,8 +35,6 @@ MATCHER_P(withName, N, "") { return arg.name == N; }
 MATCHER_P(withKind, Kind, "") { return arg.kind == Kind; }
 MATCHER_P(withDetail, Detail, "") { return arg.detail == Detail; }
 MATCHER_P(symRange, Range, "") { return arg.range == Range; }
-MATCHER_P(withSymbolTag, Tag, "") { return llvm::is_contained(arg.tags, Tag); }
-
 
 // GMock helpers for matching DocumentSymbol.
 MATCHER_P(symNameRange, Range, "") { return arg.selectionRange == Range; }
@@ -45,6 +43,12 @@ ::testing::Matcher<DocumentSymbol> children(ChildMatchers... ChildrenM) {
   return Field(&DocumentSymbol::children, UnorderedElementsAre(ChildrenM...));
 }
 
+template <typename... Tags>
+::testing::Matcher<DocumentSymbol> withSymbolTags(Tags... tags) {
+  // Matches the tags vector ignoring element order.
+  return Field(&DocumentSymbol::tags, UnorderedElementsAre(tags...));
+}
+
 std::vector<SymbolInformation> getSymbols(TestTU &TU, llvm::StringRef Query,
                                           int Limit = 0) {
   auto SymbolInfos = getWorkspaceSymbols(Query, Limit, TU.index().get(),
@@ -1147,13 +1151,13 @@ TEST(DocumentSymbolsTest, SymbolTags) {
   EXPECT_THAT(
       Symbols,
       ElementsAre(AllOf(
-          withName("A"), withSymbolTag(SymbolTag::Abstract),
-          withSymbolTag(SymbolTag::Definition),
-          children(AllOf(withName("f"), withSymbolTag(SymbolTag::ReadOnly),
-                         withSymbolTag(SymbolTag::Virtual),
-                         withSymbolTag(SymbolTag::Abstract),
-                         withSymbolTag(SymbolTag::Declaration),
-                         withSymbolTag(SymbolTag::Private))))));
+          withName("A"), withSymbolTags(SymbolTag::Abstract, SymbolTag::Definition, SymbolTag::Declaration),
+          children(AllOf(withName("f"), 
+          withSymbolTags(SymbolTag::ReadOnly,
+            SymbolTag::Virtual,
+            SymbolTag::Abstract,
+            SymbolTag::Declaration,
+            SymbolTag::Private))))));
 }
 
 } // namespace

>From 438b02fb7029abad0ef78ccd3044193b57d80967 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 12:26:35 +0100
Subject: [PATCH 26/42] Fixed tests.

---
 .../clangd/test/symbol-tags.test              | 40 +++++++++----------
 clang-tools-extra/clangd/test/symbols.test    |  4 +-
 2 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index b59c5181cce9b..5739c355b01d7 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -38,10 +38,10 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            5,
 # CHECK-NEXT:            15,
-# CHECK-NEXT:            19,
 # CHECK-NEXT:            18,
-# CHECK-NEXT:            5
+# CHECK-NEXT:            19
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
 # CHECK-NEXT:        {
@@ -69,10 +69,10 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            15,
+# CHECK-NEXT:            5,
 # CHECK-NEXT:            9,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            5
+# CHECK-NEXT:            15,
+# CHECK-NEXT:            18
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
 # CHECK-NEXT:        {
@@ -100,9 +100,9 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            20,
+# CHECK-NEXT:            5,
 # CHECK-NEXT:            18,
-# CHECK-NEXT:            5
+# CHECK-NEXT:            20
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
 # CHECK-NEXT:        {
@@ -130,9 +130,9 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            19,
+# CHECK-NEXT:            4,
 # CHECK-NEXT:            18,
-# CHECK-NEXT:            4
+# CHECK-NEXT:            19
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
 # CHECK-NEXT:        {
@@ -160,10 +160,10 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
+# CHECK-NEXT:            2,
 # CHECK-NEXT:            8,
-# CHECK-NEXT:            19,
 # CHECK-NEXT:            18,
-# CHECK-NEXT:            2
+# CHECK-NEXT:            19
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
 # CHECK-NEXT:      ],
@@ -192,8 +192,8 @@
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        9,
-# CHECK-NEXT:        19,
-# CHECK-NEXT:        18
+# CHECK-NEXT:        18,
+# CHECK-NEXT:        19
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
 # CHECK-NEXT:    {
@@ -221,10 +221,10 @@
 # CHECK-NEXT:        }
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        20,
-# CHECK-NEXT:        19,
+# CHECK-NEXT:        5,
 # CHECK-NEXT:        18,
-# CHECK-NEXT:        5
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        20
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
 # CHECK-NEXT:    {
@@ -254,11 +254,11 @@
 # CHECK-NEXT:            }
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            15,
+# CHECK-NEXT:            5,
 # CHECK-NEXT:            10,
-# CHECK-NEXT:            19,
+# CHECK-NEXT:            15,
 # CHECK-NEXT:            18,
-# CHECK-NEXT:            5
+# CHECK-NEXT:            19
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
 # CHECK-NEXT:      ],
@@ -287,8 +287,8 @@
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        10,
+# CHECK-NEXT:        18,
 # CHECK-NEXT:        19
-# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    }
 # CHECK-NEXT:  ]
diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index 4d818d9d1dcf4..a16a226e48c05 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -86,8 +86,8 @@
 # CHECK-NEXT:          }
 # CHECK-NEXT:        },
 # CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        19,
-# CHECK-NEXT:        18
+# CHECK-NEXT:        18,
+# CHECK-NEXT:        19
 # CHECK-NEXT:      ]
 # CHECK-NEXT:      }
 # CHECK-NEXT:    ]

>From 53a8c8fdd0c79f7a7fb010aaaf86803259c1418c Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 12:26:58 +0100
Subject: [PATCH 27/42] Includes clean-up.

---
 clang-tools-extra/clangd/FindSymbols.cpp          | 1 -
 clang-tools-extra/clangd/SemanticHighlighting.cpp | 2 --
 clang-tools-extra/clangd/SemanticHighlighting.h   | 1 -
 3 files changed, 4 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index bfb6ca3462dcd..b338cd1d9d5d6 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -23,7 +23,6 @@
 #include "llvm/ADT/StringRef.h"
 #include <limits>
 #include <optional>
-#include <tuple>
 
 #define DEBUG_TYPE "FindSymbols"
 
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 54e19bc7146be..d40a98216edd4 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -11,7 +11,6 @@
 #include "FindSymbols.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
-#include "AST.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "support/Logger.h"
@@ -25,7 +24,6 @@
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/Type.h"
 #include "clang/AST/TypeLoc.h"
-#include "clang/Basic/LangOptions.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Sema/HeuristicResolver.h"
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index 7ba8f423308fb..d352988888a24 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -24,7 +24,6 @@
 
 #include "clang/AST/TypeBase.h"
 #include "llvm/ADT/StringRef.h"
-#include "llvm/Support/raw_ostream.h"
 
 namespace clang {
 class NamedDecl;

>From 31dc8a5b5de10e6c63fc67c84ab4c86a3a23846a Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 14:21:37 +0100
Subject: [PATCH 28/42] Removed computeSymbolTags(const NamedDecl &ND) from
 public API.

---
 clang-tools-extra/clangd/FindSymbols.h | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 81683cf13d8a6..7b65425509b39 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -55,12 +55,8 @@ llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 /// \p ND The declaration to get tags for.
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
 
-
 /// Computes symbol tags for a given NamedDecl.
-SymbolTags computeSymbolTags(const NamedDecl &ND);
-
-/// Computes symbol tags for a given NamedDecl, with additional context about
-/// whether it's a declaration or definition.
+/// \p IsDecl indicates whether ND is a declaration (true).
 SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl);
 
 } // namespace clangd

>From aa43bbf44139299a8fd1b07d4489c0b637f4e170 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 13 Jan 2026 21:07:21 +0100
Subject: [PATCH 29/42] Fix tests.

---
 clang-tools-extra/clangd/AST.cpp              | 46 +++++++++++--------
 clang-tools-extra/clangd/AST.h                |  4 ++
 .../clangd/SemanticHighlighting.cpp           | 19 ++++----
 .../clangd/test/symbol-tags.test              |  9 +++-
 clang-tools-extra/clangd/test/symbols.test    |  3 +-
 .../clangd/test/type-hierarchy.test           |  9 ++--
 6 files changed, 57 insertions(+), 33 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 6d8e919e9536e..a9ac3876754aa 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -1119,50 +1119,60 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD) {
   return Result;
 }
 
+// Backwards-compatible default behavior: determine whether this NamedDecl is
+// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
 SymbolTags computeSymbolTags(const NamedDecl &ND) {
-  SymbolTags result = 0;
+  const auto IsDef = isUniqueDefinition(&ND);
+  return computeSymbolTags(ND, true, IsDef);
+}
+
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef) {
+  SymbolTags Result = 0;
 
   if (ND.isDeprecated())
-    result |= toSymbolTagBitmask(SymbolTag::Deprecated);
+    Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
 
   if (isConst(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
+    Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
 
   if (isStatic(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Static);
+    Result |= toSymbolTagBitmask(SymbolTag::Static);
 
   if (isVirtual(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Virtual);
+    Result |= toSymbolTagBitmask(SymbolTag::Virtual);
 
   if (isAbstract(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Abstract);
+    Result |= toSymbolTagBitmask(SymbolTag::Abstract);
 
   if (isFinal(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Final);
+    Result |= toSymbolTagBitmask(SymbolTag::Final);
+
+  if (IsDecl && 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
+    // underlying declaration.
+    Result |= toSymbolTagBitmask(SymbolTag::Declaration);
 
-  // Do not treat an UnresolvedUsingValueDecl as a declaration.
-  // It's more common to think of it as a reference to the
-  // underlying declaration.
-  if (isUniqueDefinition(&ND))
-    result |= toSymbolTagBitmask(SymbolTag::Definition);
-  else if (!isa<UnresolvedUsingValueDecl>(ND))
-    result |= toSymbolTagBitmask(SymbolTag::Declaration);
+    if (IsDef)
+      Result |= toSymbolTagBitmask(SymbolTag::Definition);
+  }
 
   switch (ND.getAccess()) {
   case AS_public:
-    result |= toSymbolTagBitmask(SymbolTag::Public);
+    Result |= toSymbolTagBitmask(SymbolTag::Public);
     break;
   case AS_protected:
-    result |= toSymbolTagBitmask(SymbolTag::Protected);
+    Result |= toSymbolTagBitmask(SymbolTag::Protected);
     break;
   case AS_private:
-    result |= toSymbolTagBitmask(SymbolTag::Private);
+    Result |= toSymbolTagBitmask(SymbolTag::Private);
     break;
   default:
     break;
   }
 
-  return result;
+  return Result;
 }
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 45ff42c3391ac..53270126cf348 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -267,6 +267,10 @@ searchConstructorsInForwardingFunction(const FunctionDecl *FD);
 /// Computes symbol tags for a given NamedDecl.
 SymbolTags computeSymbolTags(const NamedDecl &ND);
 
+/// Computes symbol tags for a given NamedDecl, with additional context about
+/// whether it's a declaration or definition.
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl, bool IsDef);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 0a5f81fe3b029..4865f44993f61 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -10,6 +10,7 @@
 #include "Config.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
+#include "AST.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "support/Logger.h"
@@ -1062,7 +1063,7 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
           if (auto Mod = scopeModifier(Decl))
             Tok.addModifier(*Mod);
 
-          const auto SymbolTags = computeSymbolTags(*Decl);
+          const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl, isUniqueDefinition(Decl));
 
           if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
             Tok.addModifier(HighlightingModifier::Deprecated);
@@ -1081,20 +1082,18 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
 
           if (isDependent(Decl))
             Tok.addModifier(HighlightingModifier::DependentName);
+
           if (isDefaultLibrary(Decl))
             Tok.addModifier(HighlightingModifier::DefaultLibrary);
+
           if (isa<CXXConstructorDecl>(Decl))
             Tok.addModifier(HighlightingModifier::ConstructorOrDestructor);
 
-          if (R.IsDecl) {
-            // Do not treat an UnresolvedUsingValueDecl as a declaration.
-            // It's more common to think of it as a reference to the
-            // underlying declaration.
-            if (!isa<UnresolvedUsingValueDecl>(Decl))
-              Tok.addModifier(HighlightingModifier::Declaration);
-            if (isUniqueDefinition(Decl))
-              Tok.addModifier(HighlightingModifier::Definition);
-          }
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
+            Tok.addModifier(HighlightingModifier::Declaration);
+
+          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
+            Tok.addModifier(HighlightingModifier::Definition);
         }
       },
       AST.getHeuristicResolver());
diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index 3d4821ae32081..b59c5181cce9b 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -40,6 +40,7 @@
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            15,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            5
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
@@ -130,6 +131,7 @@
 # CHECK-NEXT:          },
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            4
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        },
@@ -160,6 +162,7 @@
 # CHECK-NEXT:          "tags": [
 # CHECK-NEXT:            8,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            2
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
@@ -189,7 +192,8 @@
 # CHECK-NEXT:      },
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        9,
-# CHECK-NEXT:        19
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
 # CHECK-NEXT:    {
@@ -219,6 +223,7 @@
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        20,
 # CHECK-NEXT:        19,
+# CHECK-NEXT:        18,
 # CHECK-NEXT:        5
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
@@ -252,6 +257,7 @@
 # CHECK-NEXT:            15,
 # CHECK-NEXT:            10,
 # CHECK-NEXT:            19,
+# CHECK-NEXT:            18,
 # CHECK-NEXT:            5
 # CHECK-NEXT:          ]
 # CHECK-NEXT:        }
@@ -282,6 +288,7 @@
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        10,
 # CHECK-NEXT:        19
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    }
 # CHECK-NEXT:  ]
diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index 89862afbff44e..34555ab002cca 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -89,7 +89,8 @@
 # CHECK-NEXT:          }
 # CHECK-NEXT:        },
 # CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        19
+# CHECK-NEXT:        19,
+# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # 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 1d04ed9a983d1..5c7a02f6e8a22 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -46,7 +46,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19
+# CHECK-NEXT:         19,
+# CHECK-NEXT:         18
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
@@ -90,7 +91,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19
+# CHECK-NEXT:         19,
+# CHECK-NEXT:         18
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
@@ -144,7 +146,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19
+# CHECK-NEXT:         19,
+# CHECK-NEXT:         18
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }

>From 6d4d8f814988445c1ac6ec203f9a480503214d44 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 14 Jan 2026 16:11:21 +0100
Subject: [PATCH 30/42] Fix tests after merge.

---
 clang-tools-extra/clangd/test/symbol-tags.test    |  5 +----
 clang-tools-extra/clangd/test/symbols.test        |  3 +--
 clang-tools-extra/clangd/test/type-hierarchy.test | 12 ++++++------
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index efdfc80058aed..5739c355b01d7 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -193,8 +193,7 @@
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        9,
 # CHECK-NEXT:        18,
-# CHECK-NEXT:        19,
-# CHECK-NEXT:        18
+# CHECK-NEXT:        19
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
 # CHECK-NEXT:    {
@@ -225,7 +224,6 @@
 # CHECK-NEXT:        5,
 # CHECK-NEXT:        18,
 # CHECK-NEXT:        19,
-# CHECK-NEXT:        18,
 # CHECK-NEXT:        20
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    },
@@ -291,7 +289,6 @@
 # CHECK-NEXT:        10,
 # CHECK-NEXT:        18,
 # CHECK-NEXT:        19
-# CHECK-NEXT:        18
 # CHECK-NEXT:      ]
 # CHECK-NEXT:    }
 # CHECK-NEXT:  ]
diff --git a/clang-tools-extra/clangd/test/symbols.test b/clang-tools-extra/clangd/test/symbols.test
index 5dbfc8c3c7958..4f0d7b8b08569 100644
--- a/clang-tools-extra/clangd/test/symbols.test
+++ b/clang-tools-extra/clangd/test/symbols.test
@@ -90,8 +90,7 @@
 # CHECK-NEXT:        },
 # CHECK-NEXT:      "tags": [
 # CHECK-NEXT:        18,
-# CHECK-NEXT:        19,
-# CHECK-NEXT:        18
+# CHECK-NEXT:        19
 # CHECK-NEXT:      ]
 # 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 5c7a02f6e8a22..c7b8a16ae51e2 100644
--- a/clang-tools-extra/clangd/test/type-hierarchy.test
+++ b/clang-tools-extra/clangd/test/type-hierarchy.test
@@ -46,8 +46,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19,
-# CHECK-NEXT:         18
+# CHECK-NEXT:         18,
+# CHECK-NEXT:         19
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
@@ -91,8 +91,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19,
-# CHECK-NEXT:         18
+# CHECK-NEXT:         18,
+# CHECK-NEXT:         19
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }
@@ -146,8 +146,8 @@
 # CHECK-NEXT:         }
 # CHECK-NEXT:       },
 # CHECK-NEXT:       "tags": [
-# CHECK-NEXT:         19,
-# CHECK-NEXT:         18
+# CHECK-NEXT:         18,
+# CHECK-NEXT:         19
 # CHECK-NEXT:       ],
 # CHECK-NEXT:       "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:     }

>From 04144e5cae16323b47401a61ef25c278ea6f8e93 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 15 Jan 2026 09:43:14 +0100
Subject: [PATCH 31/42] Fix format.

---
 clang-tools-extra/clangd/FindSymbols.cpp        |  5 +----
 clang-tools-extra/clangd/FindSymbols.h          |  2 +-
 .../clangd/SemanticHighlighting.cpp             | 13 +++++--------
 .../clangd/unittests/FindSymbolsTests.cpp       | 17 +++++++++--------
 4 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index b338cd1d9d5d6..2c5acad01166a 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -35,8 +35,6 @@ SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
 }
 } // namespace
 
-
-
 // "Static" means many things in C++, only some get the "static" modifier.
 //
 // Meanings that do:
@@ -94,7 +92,7 @@ bool isConst(const Decl *D) {
       llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
     if (isConst(llvm::cast<ValueDecl>(D)->getType()))
       return true;
-      }
+  }
   if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
     if (OCPD->isReadOnly())
       return true;
@@ -141,7 +139,6 @@ bool isFinal(const Decl *D) {
   return false;
 }
 
-
 bool isUniqueDefinition(const NamedDecl *Decl) {
   if (auto *Func = dyn_cast<FunctionDecl>(Decl))
     return Func->isThisDeclarationADefinition();
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 7b65425509b39..015c01ebc9f64 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 {
 namespace clangd {
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index d40a98216edd4..974e6239473ab 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -132,10 +132,9 @@ std::optional<HighlightingKind> kindForDecl(const NamedDecl *D,
   if (auto *VD = dyn_cast<VarDecl>(D)) {
     if (isa<ImplicitParamDecl>(VD)) // e.g. ObjC Self
       return std::nullopt;
-    return VD->isStaticDataMember()
-               ? HighlightingKind::StaticField
-               : VD->isLocalVarDecl() ? HighlightingKind::LocalVariable
-                                      : HighlightingKind::Variable;
+    return VD->isStaticDataMember() ? HighlightingKind::StaticField
+           : VD->isLocalVarDecl()   ? HighlightingKind::LocalVariable
+                                    : HighlightingKind::Variable;
   }
   if (const auto *BD = dyn_cast<BindingDecl>(D))
     return BD->getDeclContext()->isFunctionOrMethod()
@@ -1412,9 +1411,8 @@ llvm::StringRef toSemanticTokenModifier(HighlightingModifier Modifier) {
   llvm_unreachable("unhandled HighlightingModifier");
 }
 
-std::vector<SemanticTokensEdit>
-diffTokens(llvm::ArrayRef<SemanticToken> Old,
-           llvm::ArrayRef<SemanticToken> New) {
+std::vector<SemanticTokensEdit> diffTokens(llvm::ArrayRef<SemanticToken> Old,
+                                           llvm::ArrayRef<SemanticToken> New) {
   // For now, just replace everything from the first-last modification.
   // FIXME: use a real diff instead, this is bad with include-insertion.
 
@@ -1473,6 +1471,5 @@ std::vector<Range> getInactiveRegions(ParsedAST &AST) {
   return InactiveRegions;
 }
 
-
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index b0cd6a8368982..6b3a4ff56a4b8 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -1150,14 +1150,15 @@ TEST(DocumentSymbolsTest, SymbolTags) {
   auto Symbols = getSymbols(TU.build());
   EXPECT_THAT(
       Symbols,
-      ElementsAre(AllOf(
-          withName("A"), withSymbolTags(SymbolTag::Abstract, SymbolTag::Definition, SymbolTag::Declaration),
-          children(AllOf(withName("f"), 
-          withSymbolTags(SymbolTag::ReadOnly,
-            SymbolTag::Virtual,
-            SymbolTag::Abstract,
-            SymbolTag::Declaration,
-            SymbolTag::Private))))));
+      ElementsAre(
+          AllOf(withName("A"),
+                withSymbolTags(SymbolTag::Abstract, SymbolTag::Definition,
+                               SymbolTag::Declaration),
+                children(AllOf(
+                    withName("f"),
+                    withSymbolTags(SymbolTag::ReadOnly, SymbolTag::Virtual,
+                                   SymbolTag::Abstract, SymbolTag::Declaration,
+                                   SymbolTag::Private))))));
 }
 
 } // namespace

>From 64978c3f534c1670b8901c164a3b0705ad6f485e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 15 Jan 2026 16:08:33 +0100
Subject: [PATCH 32/42] Review findings: Refactored unit and integration tests.
 Robust SymbolTag declaration. Static assert of the symbol tag bit-mask's
 size. And other minor improvements...

---
 clang-tools-extra/clangd/FindSymbols.cpp      |  10 +-
 clang-tools-extra/clangd/FindSymbols.h        |   8 +-
 clang-tools-extra/clangd/Protocol.h           |   6 +
 .../clangd/SemanticHighlighting.h             |   4 -
 clang-tools-extra/clangd/XRefs.cpp            |   1 -
 .../clangd/test/symbol-tags.test              | 355 ++++--------------
 .../clangd/unittests/FindSymbolsTests.cpp     |  66 +++-
 7 files changed, 142 insertions(+), 308 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 2c5acad01166a..887c228bbf0df 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -33,7 +33,6 @@ namespace {
 SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
   return (1 << static_cast<unsigned>(ST));
 }
-} // namespace
 
 // "Static" means many things in C++, only some get the "static" modifier.
 //
@@ -139,6 +138,8 @@ bool isFinal(const Decl *D) {
   return false;
 }
 
+// Indicates whether declaration D is a unique definition (as opposed to a
+// declaration).
 bool isUniqueDefinition(const NamedDecl *Decl) {
   if (auto *Func = dyn_cast<FunctionDecl>(Decl))
     return Func->isThisDeclarationADefinition();
@@ -155,6 +156,7 @@ bool isUniqueDefinition(const NamedDecl *Decl) {
          isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
          isa<ObjCImplDecl>(Decl);
 }
+} // namespace
 
 // Backwards-compatible default behavior: determine whether this NamedDecl is
 // definition based on `isUniqueDefinition`, assuming that ND is a declaration.
@@ -220,9 +222,9 @@ std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
 
   // Iterate through SymbolTag enum values and collect any that are present in
   // the bitmask. SymbolTag values are in the numeric range
-  // [Deprecated .. ReadOnly].
-  constexpr unsigned MinTag = static_cast<unsigned>(SymbolTag::Deprecated);
-  constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::ReadOnly);
+  // [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))
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 015c01ebc9f64..0040cc6e5c078 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -22,8 +22,14 @@ namespace clangd {
 class ParsedAST;
 class SymbolIndex;
 
-/// A bitmask type representing symbol tags.
+/// 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.");
 
 /// Helper function for deriving an LSP Location from an index SymbolLocation.
 llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index b8d18ce9eee79..15faec19bc6a4 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1104,6 +1104,8 @@ struct CodeAction {
 };
 llvm::json::Value toJSON(const CodeAction &);
 
+/// Symbol tags are extra annotations that can be attached to a symbol.
+/// \see https://github.com/microsoft/language-server-protocol/pull/2003
 enum class SymbolTag {
   Deprecated = 1,
   Private = 2,
@@ -1125,6 +1127,10 @@ enum class SymbolTag {
   Declaration = 18,
   Definition = 19,
   ReadOnly = 20,
+
+  // Update as needed
+  FirstTag = Deprecated,
+  LastTag = ReadOnly
 };
 llvm::json::Value toJSON(SymbolTag);
 /// Represents programming constructs like variables, classes, interfaces etc.
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index d352988888a24..9a806c438f597 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -129,10 +129,6 @@ std::vector<SemanticTokensEdit> diffTokens(llvm::ArrayRef<SemanticToken> Before,
 // are not included.
 std::vector<Range> getInactiveRegions(ParsedAST &AST);
 
-// Indicates whether declaration D is a unique definition (as opposed to a
-// declaration).
-bool isUniqueDefinition(const NamedDecl *Decl);
-
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 2c99a54e08c96..8a24d19a7d129 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1812,7 +1812,6 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   HI.name = printName(Ctx, ND);
   // FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
   HI.kind = SK;
-  HI.tags = getSymbolTags(ND);
   HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
                    sourceLocToPosition(SM, DeclRange->getEnd())};
   HI.selectionRange = Range{NameBegin, NameEnd};
diff --git a/clang-tools-extra/clangd/test/symbol-tags.test b/clang-tools-extra/clangd/test/symbol-tags.test
index 5739c355b01d7..6b17dc994d029 100644
--- a/clang-tools-extra/clangd/test/symbol-tags.test
+++ b/clang-tools-extra/clangd/test/symbol-tags.test
@@ -5,294 +5,79 @@
 ---
 {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,
     "text":
-    "class AbstractClass{\n public:\n virtual ~AbstractClass() = default;\n virtual void f1() = 0;\n void f2() const;\n protected: \n void f3(){}\n private: \n static void f4(){} \n }; void AbstractClass::f2() const {} \n class ImplClass final: public AbstractClass { \n public: \n void f1() final {}};"
+    "class A {\n virtual void f() const = 0;\n};"
 }}}
 ---
 {"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}}
-# CHECK:  "id": 2,
-# CHECK-NEXT:  "jsonrpc": "2.0",
-# CHECK-NEXT:    "result": [
-# CHECK-NEXT:      {
-# CHECK-NEXT:        "children": [
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "kind": 9,
-# CHECK-NEXT:          "name": "~AbstractClass",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 35,
-# CHECK-NEXT:              "line": 2
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 2
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 10,
-# CHECK-NEXT:              "line": 2
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 9,
-# CHECK-NEXT:              "line": 2
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            5,
-# CHECK-NEXT:            15,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            19
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        },
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "detail": "void ()",
-# CHECK-NEXT:          "kind": 6,
-# CHECK-NEXT:          "name": "f1",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 22,
-# CHECK-NEXT:              "line": 3
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 3
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 16,
-# CHECK-NEXT:              "line": 3
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 14,
-# CHECK-NEXT:              "line": 3
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            5,
-# CHECK-NEXT:            9,
-# CHECK-NEXT:            15,
-# CHECK-NEXT:            18
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        },
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "detail": "void () const",
-# CHECK-NEXT:          "kind": 6,
-# CHECK-NEXT:          "name": "f2",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 16,
-# CHECK-NEXT:              "line": 4
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 4
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 8,
-# CHECK-NEXT:              "line": 4
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 6,
-# CHECK-NEXT:              "line": 4
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            5,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            20
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        },
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "detail": "void ()",
-# CHECK-NEXT:          "kind": 6,
-# CHECK-NEXT:          "name": "f3",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 12,
-# CHECK-NEXT:              "line": 6
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 6
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 8,
-# CHECK-NEXT:              "line": 6
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 6,
-# CHECK-NEXT:              "line": 6
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            4,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            19
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        },
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "detail": "void ()",
-# CHECK-NEXT:          "kind": 6,
-# CHECK-NEXT:          "name": "f4",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 19,
-# CHECK-NEXT:              "line": 8
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 8
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 15,
-# CHECK-NEXT:              "line": 8
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 13,
-# CHECK-NEXT:              "line": 8
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            2,
-# CHECK-NEXT:            8,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            19
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        }
-# CHECK-NEXT:      ],
-# CHECK-NEXT:      "detail": "class",
-# CHECK-NEXT:      "kind": 5,
-# CHECK-NEXT:      "name": "AbstractClass",
-# CHECK-NEXT:      "range": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 2,
-# CHECK-NEXT:          "line": 9
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 0,
-# CHECK-NEXT:          "line": 0
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "selectionRange": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 19,
-# CHECK-NEXT:          "line": 0
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 6,
-# CHECK-NEXT:          "line": 0
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        9,
-# CHECK-NEXT:        18,
-# CHECK-NEXT:        19
-# CHECK-NEXT:      ]
-# CHECK-NEXT:    },
-# CHECK-NEXT:    {
-# CHECK-NEXT:      "detail": "void () const",
-# CHECK-NEXT:      "kind": 6,
-# CHECK-NEXT:      "name": "AbstractClass::f2",
-# CHECK-NEXT:      "range": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 37,
-# CHECK-NEXT:          "line": 9
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 4,
-# CHECK-NEXT:          "line": 9
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "selectionRange": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 26,
-# CHECK-NEXT:          "line": 9
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 24,
-# CHECK-NEXT:          "line": 9
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        5,
-# CHECK-NEXT:        18,
-# CHECK-NEXT:        19,
-# CHECK-NEXT:        20
-# CHECK-NEXT:      ]
-# CHECK-NEXT:    },
-# CHECK-NEXT:    {
-# CHECK-NEXT:      "children": [
-# CHECK-NEXT:        {
-# CHECK-NEXT:          "detail": "void ()",
-# CHECK-NEXT:          "kind": 6,
-# CHECK-NEXT:          "name": "f1",
-# CHECK-NEXT:          "range": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 19,
-# CHECK-NEXT:              "line": 12
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 1,
-# CHECK-NEXT:              "line": 12
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "selectionRange": {
-# CHECK-NEXT:            "end": {
-# CHECK-NEXT:              "character": 8,
-# CHECK-NEXT:              "line": 12
-# CHECK-NEXT:            },
-# CHECK-NEXT:            "start": {
-# CHECK-NEXT:              "character": 6,
-# CHECK-NEXT:              "line": 12
-# CHECK-NEXT:            }
-# CHECK-NEXT:          },
-# CHECK-NEXT:          "tags": [
-# CHECK-NEXT:            5,
-# CHECK-NEXT:            10,
-# CHECK-NEXT:            15,
-# CHECK-NEXT:            18,
-# CHECK-NEXT:            19
-# CHECK-NEXT:          ]
-# CHECK-NEXT:        }
-# CHECK-NEXT:      ],
-# CHECK-NEXT:      "detail": "class",
-# CHECK-NEXT:      "kind": 5,
-# CHECK-NEXT:      "name": "ImplClass",
-# CHECK-NEXT:      "range": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 20,
-# CHECK-NEXT:          "line": 12
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 1,
-# CHECK-NEXT:          "line": 10
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "selectionRange": {
-# CHECK-NEXT:        "end": {
-# CHECK-NEXT:          "character": 16,
-# CHECK-NEXT:          "line": 10
-# CHECK-NEXT:        },
-# CHECK-NEXT:        "start": {
-# CHECK-NEXT:          "character": 7,
-# CHECK-NEXT:          "line": 10
-# CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        10,
-# CHECK-NEXT:        18,
-# CHECK-NEXT:        19
-# CHECK-NEXT:      ]
-# CHECK-NEXT:    }
-# CHECK-NEXT:  ]
-# CHECK-NEXT:}
+# CHECK:  "id": 2
+# CHECK:   "jsonrpc": "2.0",
+# CHECK:   "result": [
+# CHECK:     {
+# CHECK:       "children": [
+# CHECK:         {
+# CHECK:           "detail": "void () const",
+# CHECK:           "kind": 6,
+# CHECK:           "name": "f",
+# CHECK:           "range": {
+# CHECK:             "end": {
+# CHECK:               "character": 27,
+# CHECK:               "line": 1
+# CHECK:             },
+# CHECK:             "start": {
+# CHECK:               "character": 1,
+# CHECK:               "line": 1
+# CHECK:             }
+# CHECK:           },
+# CHECK:           "selectionRange": {
+# CHECK:             "end": {
+# CHECK:               "character": 15,
+# CHECK:               "line": 1
+# CHECK:             },
+# CHECK:             "start": {
+# CHECK:               "character": 14,
+# CHECK:               "line": 1
+# CHECK:             }
+# CHECK:           },
+# CHECK:           "tags": [
+# CHECK:             2,
+# CHECK:             9,
+# CHECK:             15,
+# CHECK:             18,
+# CHECK:             20
+# CHECK:           ]
+# CHECK:         }
+# CHECK:       ],
+# CHECK:       "detail": "class",
+# CHECK:       "kind": 5,
+# CHECK:       "name": "A",
+# CHECK:       "range": {
+# CHECK:         "end": {
+# CHECK:           "character": 1,
+# CHECK:           "line": 2
+# CHECK:         },
+# CHECK:         "start": {
+# CHECK:           "character": 0,
+# CHECK:           "line": 0
+# CHECK:         }
+# CHECK:       },
+# CHECK:       "selectionRange": {
+# CHECK:         "end": {
+# CHECK:           "character": 7,
+# CHECK:           "line": 0
+# CHECK:         },
+# CHECK:         "start": {
+# CHECK:           "character": 6,
+# CHECK:           "line": 0
+# CHECK:         }
+# CHECK:       },
+# CHECK:       "tags": [
+# CHECK:         9,
+# CHECK:         18,
+# CHECK:         19
+# CHECK:       ]
+# CHECK:     }
+# CHECK:   ]
+# CHECK: }
 
 ---
 {"jsonrpc":"2.0","id":3,"method":"shutdown"}
diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
index 6b3a4ff56a4b8..d0dd5d0f8f434 100644
--- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -1140,25 +1140,65 @@ TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) {
 
 TEST(DocumentSymbolsTest, SymbolTags) {
   TestTU TU;
-  Annotations Main(R"(
-      class A {
-        virtual void f() const = 0;
-      };
-    )");
+  Annotations Main(R"cpp(
+    class AbstractClass {
+      public:
+        virtual ~AbstractClass() = default;
+        virtual void f1() = 0;
+        void f2() const;
+      protected:
+        void f3(){}
+      private:
+        static void f4(){}
+    };
+
+    void AbstractClass::f2() const {}
+
+    class ImplClass final: public AbstractClass {
+      public:
+        void f1() final {}
+    };
+    )cpp");
 
   TU.Code = Main.code().str();
   auto Symbols = getSymbols(TU.build());
   EXPECT_THAT(
       Symbols,
-      ElementsAre(
-          AllOf(withName("A"),
-                withSymbolTags(SymbolTag::Abstract, SymbolTag::Definition,
-                               SymbolTag::Declaration),
+      UnorderedElementsAre(
+          AllOf(
+              withName("AbstractClass"),
+              withSymbolTags(SymbolTag::Abstract, SymbolTag::Declaration,
+                             SymbolTag::Definition),
+              children(
+                  AllOf(withName("~AbstractClass"),
+                        withSymbolTags(SymbolTag::Public, SymbolTag::Virtual,
+                                       SymbolTag::Declaration,
+                                       SymbolTag::Definition)),
+                  AllOf(withName("f1"),
+                        withSymbolTags(SymbolTag::Public, SymbolTag::Abstract,
+                                       SymbolTag::Virtual,
+                                       SymbolTag::Declaration)),
+                  AllOf(withName("f2"), withSymbolTags(SymbolTag::Public,
+                                                       SymbolTag::Declaration,
+                                                       SymbolTag::ReadOnly)),
+                  AllOf(withName("f3"), withSymbolTags(SymbolTag::Protected,
+                                                       SymbolTag::Declaration,
+                                                       SymbolTag::Definition)),
+                  AllOf(withName("f4"),
+                        withSymbolTags(SymbolTag::Private, SymbolTag::Static,
+                                       SymbolTag::Declaration,
+                                       SymbolTag::Definition)))),
+          AllOf(withName("AbstractClass::f2"),
+                withSymbolTags(SymbolTag::Public, SymbolTag::Declaration,
+                               SymbolTag::Definition, SymbolTag::ReadOnly)),
+          AllOf(withName("ImplClass"),
+                withSymbolTags(SymbolTag::Final, SymbolTag::Declaration,
+                               SymbolTag::Definition),
                 children(AllOf(
-                    withName("f"),
-                    withSymbolTags(SymbolTag::ReadOnly, SymbolTag::Virtual,
-                                   SymbolTag::Abstract, SymbolTag::Declaration,
-                                   SymbolTag::Private))))));
+                    withName("f1"),
+                    withSymbolTags(SymbolTag::Public, SymbolTag::Final,
+                                   SymbolTag::Virtual, SymbolTag::Declaration,
+                                   SymbolTag::Definition))))));
 }
 
 } // namespace

>From b265a9c6d3ba3c94ffd6b10f11f0cbca079f154b Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 15 Jan 2026 21:33:45 +0100
Subject: [PATCH 33/42] Fix test.

---
 clang-tools-extra/clangd/test/call-hierarchy.test | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index a25731f03b4c5..c81a26bdcd672 100644
--- a/clang-tools-extra/clangd/test/call-hierarchy.test
+++ b/clang-tools-extra/clangd/test/call-hierarchy.test
@@ -30,10 +30,7 @@
 # CHECK-NEXT:          "character": 5,
 # CHECK-NEXT:          "line": 0
 # CHECK-NEXT:        }
-# CHECK-NEXT:      },
-# CHECK-NEXT:      "tags": [
-# CHECK-NEXT:        18
-# CHECK-NEXT:      ],
+# CHECK-NEXT:      }
 # CHECK-NEXT:      "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:    }
 ---

>From bd233a463dcf757d359c7bf1b4f4093c9000733d Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Mon, 19 Jan 2026 09:55:36 +0100
Subject: [PATCH 34/42] Review findings: refactored computeSymbolTags - no
 IsDecl is needed, refactored getSemanticHighlightings - intro map for tags
 and modifiers,

---
 clang-tools-extra/clangd/FindSymbols.cpp      |  8 +---
 clang-tools-extra/clangd/FindSymbols.h        |  3 +-
 .../clangd/SemanticHighlighting.cpp           | 42 ++++++++++---------
 3 files changed, 24 insertions(+), 29 deletions(-)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 887c228bbf0df..6cf5927725eae 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -158,13 +158,7 @@ bool isUniqueDefinition(const NamedDecl *Decl) {
 }
 } // namespace
 
-// Backwards-compatible default behavior: determine whether this NamedDecl is
-// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
 SymbolTags computeSymbolTags(const NamedDecl &ND) {
-  return computeSymbolTags(ND, true);
-}
-
-SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl) {
   SymbolTags Result = 0;
   const auto IsDef = isUniqueDefinition(&ND);
 
@@ -186,7 +180,7 @@ SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl) {
   if (isFinal(&ND))
     Result |= toSymbolTagBitmask(SymbolTag::Final);
 
-  if (IsDecl && not isa<UnresolvedUsingValueDecl>(ND)) {
+  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
     // underlying declaration.
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 0040cc6e5c078..7fefead17056f 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -62,8 +62,7 @@ llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
 
 /// Computes symbol tags for a given NamedDecl.
-/// \p IsDecl indicates whether ND is a declaration (true).
-SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl);
+SymbolTags computeSymbolTags(const NamedDecl &ND);
 
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 974e6239473ab..70243027f83f5 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -1061,22 +1061,30 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
           if (auto Mod = scopeModifier(Decl))
             Tok.addModifier(*Mod);
 
-          const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl);
-
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Deprecated))
-            Tok.addModifier(HighlightingModifier::Deprecated);
-
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::ReadOnly))
-            Tok.addModifier(HighlightingModifier::Readonly);
-
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Static))
-            Tok.addModifier(HighlightingModifier::Static);
+          const auto SymbolTags = computeSymbolTags(*Decl);
+
+          static const llvm::DenseMap<SymbolTag, HighlightingModifier>
+              TagModifierMap = {
+                  {SymbolTag::Deprecated, HighlightingModifier::Deprecated},
+                  {SymbolTag::ReadOnly, HighlightingModifier::Readonly},
+                  {SymbolTag::Static, HighlightingModifier::Static},
+                  {SymbolTag::Virtual, HighlightingModifier::Virtual},
+                  {SymbolTag::Abstract, HighlightingModifier::Abstract},
+                  // Declaration and Definition are handled separately below.
+              };
+
+          for (const auto &[Tag, Modifier] : TagModifierMap) {
+            if (SymbolTags & toSymbolTagBitmask(Tag))
+              Tok.addModifier(Modifier);
+          }
 
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Virtual))
-            Tok.addModifier(HighlightingModifier::Virtual);
+          if (R.IsDecl &&
+              (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))) {
+            Tok.addModifier(HighlightingModifier::Declaration);
 
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Abstract))
-            Tok.addModifier(HighlightingModifier::Abstract);
+            if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
+              Tok.addModifier(HighlightingModifier::Definition);
+          }
 
           if (isDependent(Decl))
             Tok.addModifier(HighlightingModifier::DependentName);
@@ -1086,12 +1094,6 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
 
           if (isa<CXXConstructorDecl>(Decl))
             Tok.addModifier(HighlightingModifier::ConstructorOrDestructor);
-
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
-            Tok.addModifier(HighlightingModifier::Declaration);
-
-          if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
-            Tok.addModifier(HighlightingModifier::Definition);
         }
       },
       AST.getHeuristicResolver());

>From 2eb7d3bed3705b349aca8d5e2671ff96cd44eeed Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 20 Jan 2026 14:02:29 +0100
Subject: [PATCH 35/42] Review findings: - obsolete includes, - public
 toSymbolTagBitmask, - removed tags from TypeHierarchyItem, - thread_local map
 in getSemanticHighlightings - minor improvements

---
 clang-tools-extra/clangd/AST.cpp                  |  2 --
 clang-tools-extra/clangd/AST.h                    |  1 -
 clang-tools-extra/clangd/FindSymbols.cpp          |  7 ++++---
 clang-tools-extra/clangd/FindSymbols.h            | 11 ++++++++---
 clang-tools-extra/clangd/Protocol.h               |  3 ---
 clang-tools-extra/clangd/SemanticHighlighting.cpp |  7 +------
 clang-tools-extra/clangd/test/call-hierarchy.test |  2 +-
 7 files changed, 14 insertions(+), 19 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 3f96faba18cf3..3bcc89d360cdb 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -8,7 +8,6 @@
 
 #include "AST.h"
 
-#include "SemanticHighlighting.h"
 #include "SourceCode.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTTypeTraits.h"
@@ -618,7 +617,6 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
 
   QualType DeducedType;
 };
-
 } // namespace
 
 std::optional<QualType> getDeducedType(ASTContext &ASTCtx,
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 2bb4943b6de0b..193c122222684 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -17,7 +17,6 @@
 #include "index/Symbol.h"
 #include "index/SymbolID.h"
 #include "clang/AST/Decl.h"
-#include "clang/AST/DeclObjC.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/MacroInfo.h"
diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 6cf5927725eae..243746056aed0 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -30,9 +30,6 @@ namespace clang {
 namespace clangd {
 
 namespace {
-SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
-  return (1 << static_cast<unsigned>(ST));
-}
 
 // "Static" means many things in C++, only some get the "static" modifier.
 //
@@ -158,6 +155,10 @@ bool isUniqueDefinition(const NamedDecl *Decl) {
 }
 } // namespace
 
+SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
+  return (1 << static_cast<unsigned>(ST));
+}
+
 SymbolTags computeSymbolTags(const NamedDecl &ND) {
   SymbolTags Result = 0;
   const auto IsDef = isUniqueDefinition(&ND);
diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h
index 7fefead17056f..97b99af4f35e6 100644
--- a/clang-tools-extra/clangd/FindSymbols.h
+++ b/clang-tools-extra/clangd/FindSymbols.h
@@ -57,13 +57,18 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
 /// same order that they appear.
 llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
 
-/// Returns the symbol tags for the given declaration.
-/// \p ND The declaration to get tags for.
-std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
+/// Converts a single SymbolTag to a bitmask.
+SymbolTags toSymbolTagBitmask(SymbolTag ST);
 
 /// Computes symbol tags for a given NamedDecl.
 SymbolTags computeSymbolTags(const NamedDecl &ND);
 
+/// Returns the symbol tags for the given declaration.
+/// This is a wrapper around computeSymbolTags() which unpacks
+/// the tags into a vector.
+/// \p ND The declaration to get tags for.
+std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 15faec19bc6a4..a88c9a391f97a 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1548,9 +1548,6 @@ struct TypeHierarchyItem {
   /// The kind of this item.
   SymbolKind kind;
 
-  /// The symbol tags for this item.
-  std::vector<SymbolTag> tags;
-
   /// More detail for this item, e.g. the signature of a function.
   std::optional<std::string> detail;
 
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 70243027f83f5..ce1f51a681ff4 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -22,7 +22,6 @@
 #include "clang/AST/DeclarationName.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/RecursiveASTVisitor.h"
-#include "clang/AST/Type.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
@@ -1025,10 +1024,6 @@ class CollectExtraHighlightings
 private:
   HighlightingsBuilder &H;
 };
-
-SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
-  return (1 << static_cast<unsigned>(ST));
-}
 } // namespace
 
 std::vector<HighlightingToken>
@@ -1063,7 +1058,7 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
 
           const auto SymbolTags = computeSymbolTags(*Decl);
 
-          static const llvm::DenseMap<SymbolTag, HighlightingModifier>
+          static const thread_local llvm::DenseMap<SymbolTag, HighlightingModifier>
               TagModifierMap = {
                   {SymbolTag::Deprecated, HighlightingModifier::Deprecated},
                   {SymbolTag::ReadOnly, HighlightingModifier::Readonly},
diff --git a/clang-tools-extra/clangd/test/call-hierarchy.test b/clang-tools-extra/clangd/test/call-hierarchy.test
index c81a26bdcd672..6548ea0068a8d 100644
--- a/clang-tools-extra/clangd/test/call-hierarchy.test
+++ b/clang-tools-extra/clangd/test/call-hierarchy.test
@@ -30,7 +30,7 @@
 # CHECK-NEXT:          "character": 5,
 # CHECK-NEXT:          "line": 0
 # CHECK-NEXT:        }
-# CHECK-NEXT:      }
+# CHECK-NEXT:      },
 # CHECK-NEXT:      "uri": "file://{{.*}}/clangd-test/main.cpp"
 # CHECK-NEXT:    }
 ---

>From 333355020e5176eff65d7458ece58f96b6433986 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Tue, 20 Jan 2026 20:12:48 +0100
Subject: [PATCH 36/42] Fix format.

---
 clang-tools-extra/clangd/SemanticHighlighting.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index ce1f51a681ff4..79446a166d0d8 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "SemanticHighlighting.h"
+#include "AST.h"
 #include "Config.h"
 #include "FindSymbols.h"
 #include "FindTarget.h"
@@ -32,7 +33,6 @@
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
 
-#include <AST.h>
 #include <algorithm>
 #include <optional>
 
@@ -1058,7 +1058,8 @@ getSemanticHighlightings(ParsedAST &AST, bool IncludeInactiveRegionTokens) {
 
           const auto SymbolTags = computeSymbolTags(*Decl);
 
-          static const thread_local llvm::DenseMap<SymbolTag, HighlightingModifier>
+          static const thread_local llvm::DenseMap<SymbolTag,
+                                                   HighlightingModifier>
               TagModifierMap = {
                   {SymbolTag::Deprecated, HighlightingModifier::Deprecated},
                   {SymbolTag::ReadOnly, HighlightingModifier::Readonly},

>From 49a4a783b16d6ec3dbe5fd26bf6cc0c5196e3f6c Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 21 Jan 2026 09:54:11 +0100
Subject: [PATCH 37/42] Review findings: - removed obsolete forward-decl and
 includes - restored include

---
 clang-tools-extra/clangd/AST.h                  | 1 +
 clang-tools-extra/clangd/SemanticHighlighting.h | 4 ----
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 193c122222684..2bb4943b6de0b 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -17,6 +17,7 @@
 #include "index/Symbol.h"
 #include "index/SymbolID.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/MacroInfo.h"
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index 9a806c438f597..ba25c9a7ee6be 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -21,13 +21,9 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICHIGHLIGHTING_H
 
 #include "Protocol.h"
-
-#include "clang/AST/TypeBase.h"
 #include "llvm/ADT/StringRef.h"
 
 namespace clang {
-class NamedDecl;
-
 namespace clangd {
 class ParsedAST;
 

>From facb36ae911fe8dabe9f0a3afe3cadb5ff32e355 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Wed, 21 Jan 2026 11:30:50 +0100
Subject: [PATCH 38/42] Merge-Fix

---
 clang-tools-extra/clangd/Protocol.h               | 3 +++
 clang-tools-extra/clangd/XRefs.cpp                | 1 +
 clang-tools-extra/clangd/test/call-hierarchy.test | 3 +++
 3 files changed, 7 insertions(+)

diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index a88c9a391f97a..15faec19bc6a4 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1548,6 +1548,9 @@ struct TypeHierarchyItem {
   /// The kind of this item.
   SymbolKind kind;
 
+  /// The symbol tags for this item.
+  std::vector<SymbolTag> tags;
+
   /// More detail for this item, e.g. the signature of a function.
   std::optional<std::string> detail;
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index c8afbe0b67dfc..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};
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:    }
 ---

>From 73634a18a0860004a4209b5b9adffe8b2cb8d6b7 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Thu, 22 Jan 2026 13:18:40 +0100
Subject: [PATCH 39/42] Extended unit-tests to check the occurrence of symbol
 tags in call-hierarchy and type-hierarchy.

---
 .../clangd/unittests/CallHierarchyTests.cpp   | 56 +++++++++++++++++++
 .../clangd/unittests/TypeHierarchyTests.cpp   | 30 ++++++----
 2 files changed, 76 insertions(+), 10 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
index 2891b420427d2..1d2ae22463a08 100644
--- a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
@@ -48,6 +48,12 @@ MATCHER_P(withDetail, N, "") { return arg.detail == N; }
 MATCHER_P(withFile, N, "") { return arg.uri.file() == N; }
 MATCHER_P(withSelectionRange, R, "") { return arg.selectionRange == R; }
 
+template <typename... Tags>
+::testing::Matcher<CallHierarchyItem> withSymbolTags(Tags... tags) {
+  // Matches the tags vector ignoring element order.
+  return Field(&CallHierarchyItem::tags, UnorderedElementsAre(tags...));
+}
+
 template <class ItemMatcher>
 ::testing::Matcher<CallHierarchyIncomingCall> from(ItemMatcher M) {
   return Field(&CallHierarchyIncomingCall::from, M);
@@ -728,6 +734,56 @@ TEST(CallHierarchy, CallInDifferentFileThanCaller) {
               ElementsAre(AllOf(from(withName("caller")), iFromRanges())));
 }
 
+TEST(CallHierarchy, IncomingCalls) {
+  Annotations Source(R"cpp(
+    class A {
+      public:
+        void call^ee() {};
+    };
+    void caller(A &a) {
+      a.callee();
+    }
+  )cpp");
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+  auto Index = TU.index();
+
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(withName("callee")));
+
+  auto Incoming = incomingCalls(Items[0], Index.get(), AST);
+  EXPECT_THAT(
+      Incoming,
+      UnorderedElementsAre(AllOf(from(
+          AllOf(withName("caller"), withSymbolTags(SymbolTag::Declaration,
+                                                   SymbolTag::Definition))))));
+}
+
+TEST(CallHierarchy, OutgoingCalls) {
+  Annotations Source(R"cpp(
+    void callee() {}
+    class A {
+      public:
+        void call^er() {
+          callee();
+        };
+    };
+  )cpp");
+  TestTU TU = TestTU::withCode(Source.code());
+  auto AST = TU.build();
+  auto Index = TU.index();
+
+  std::vector<CallHierarchyItem> Items =
+      prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
+  ASSERT_THAT(Items, ElementsAre(withName("caller")));
+
+  auto Outgoing = outgoingCalls(Items[0], Index.get(), AST);
+  EXPECT_THAT(Outgoing, UnorderedElementsAre(AllOf(
+                            to(AllOf(withName("callee"),
+                                     withSymbolTags(SymbolTag::Declaration,
+                                                    SymbolTag::Definition))))));
+}
 } // namespace
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
index 8696e6d4b0790..21adbb6d0d3a3 100644
--- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
+++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
@@ -54,6 +54,12 @@ MATCHER_P(withResolveParents, M, "") {
   return testing::ExplainMatchResult(M, arg.data.parents, result_listener);
 }
 
+template <typename... Tags>
+::testing::Matcher<TypeHierarchyItem> withSymbolTags(Tags... tags) {
+  // Matches the tags vector ignoring element order.
+  return Field(&TypeHierarchyItem::tags, UnorderedElementsAre(tags...));
+}
+
 TEST(FindRecordTypeAt, TypeOrVariable) {
   Annotations Source(R"cpp(
 struct Ch^ild2 {
@@ -770,10 +776,10 @@ struct Child2b : Child1 {};
 
 TEST(Standard, SubTypes) {
   Annotations Source(R"cpp(
-struct Pare^nt1 {};
-struct Parent2 {};
-struct Child : Parent1, Parent2 {};
-)cpp");
+    struct Pare^nt1 {};
+    struct Parent2 {};
+    struct Child final : Parent1, Parent2 {};
+  )cpp");
 
   TestTU TU = TestTU::withCode(Source.code());
   auto AST = TU.build();
@@ -791,15 +797,17 @@ struct Child : Parent1, Parent2 {};
       Children,
       UnorderedElementsAre(
           AllOf(withName("Child"),
+                withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition,
+                               SymbolTag::Final),
                 withResolveParents(Optional(UnorderedElementsAre(withResolveID(
                     getSymbolID(&findDecl(AST, "Parent1")).str())))))));
 }
 
 TEST(Standard, SuperTypes) {
   Annotations Source(R"cpp(
-struct Parent {};
-struct Chil^d : Parent {};
-)cpp");
+    struct Parent {};
+    struct Chil^d : Parent {};
+  )cpp");
 
   TestTU TU = TestTU::withCode(Source.code());
   auto AST = TU.build();
@@ -811,9 +819,11 @@ struct Chil^d : Parent {};
   ASSERT_THAT(Result, SizeIs(1));
   auto Parents = superTypes(Result.front(), Index.get(), AST);
 
-  EXPECT_THAT(Parents, Optional(UnorderedElementsAre(
-                           AllOf(withName("Parent"),
-                                 withResolveParents(Optional(IsEmpty()))))));
+  EXPECT_THAT(Parents,
+              Optional(UnorderedElementsAre(AllOf(
+                  withName("Parent"),
+                  withSymbolTags(SymbolTag::Declaration, SymbolTag::Definition),
+                  withResolveParents(Optional(IsEmpty()))))));
 }
 } // namespace
 } // namespace clangd

>From 59322919303df07c8309fcd3309a9434eaed2b85 Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 23 Jan 2026 15:32:55 +0100
Subject: [PATCH 40/42] Add symbol tags to SymbolInfo in FindSymbols.

---
 clang-tools-extra/clangd/FindSymbols.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 06a32e58fd489..7fe91f883c79f 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -376,6 +376,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())

>From 16bbb43df44ec6e460dea4071f792e2d2ddccc0a Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 23 Jan 2026 15:46:57 +0100
Subject: [PATCH 41/42] Fix code format.

---
 clang-tools-extra/clangd/Protocol.cpp         |  6 ++--
 .../clangd/SemanticHighlighting.cpp           |  1 -
 .../clangd/unittests/CallHierarchyTests.cpp   | 28 +++++++++++--------
 3 files changed, 20 insertions(+), 15 deletions(-)

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/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 5aa3a70363165..79446a166d0d8 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -12,7 +12,6 @@
 #include "FindSymbols.h"
 #include "FindTarget.h"
 #include "ParsedAST.h"
-#include "AST.h"
 #include "Protocol.h"
 #include "SourceCode.h"
 #include "support/Logger.h"
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 563304404b0c1dfb2ebcdee294c6cd2abdee6d8c Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <dimitri.ratz at thinkdigital.cc>
Date: Fri, 23 Jan 2026 17:34:15 +0100
Subject: [PATCH 42/42] Simplify lambda capture by explicitly moving `Item` and
 parameters.

---
 clang-tools-extra/clangd/ClangdServer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 5b6525d8f842e..e304db2914483 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -899,7 +899,7 @@ void ClangdServer::resolveTypeHierarchy(
     PathRef File, TypeHierarchyItem Item, int Resolve,
     TypeHierarchyDirection Direction,
     Callback<std::optional<TypeHierarchyItem>> CB) {
-  auto Action = [=, CB = std::move(CB),
+  auto Action = [Item = std::move(Item), Resolve, Direction, CB = std::move(CB),
                  this](llvm::Expected<InputsAndAST> InpAST) mutable {
     if (!InpAST)
       return CB(InpAST.takeError());



More information about the cfe-commits mailing list