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

>From 103b18fd135e523ee323ce6dc7e09d5ef2eb7f6a Mon Sep 17 00:00:00 2001
From: chouzz <[email protected]>
Date: Fri, 25 Oct 2024 17:42:04 +0800
Subject: [PATCH 01/13] [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 <[email protected]>
Date: Mon, 28 Oct 2024 20:14:15 +0800
Subject: [PATCH 02/13] 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 <[email protected]>
Date: Thu, 31 Oct 2024 19:47:47 +0800
Subject: [PATCH 03/13] 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 <[email protected]>
Date: Mon, 18 Nov 2024 14:43:14 +0800
Subject: [PATCH 04/13] 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 <[email protected]>
Date: Tue, 11 Nov 2025 23:05:47 +0100
Subject: [PATCH 05/13] 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 <[email protected]>
Date: Wed, 12 Nov 2025 16:14:55 +0100
Subject: [PATCH 06/13] 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 <[email protected]>
Date: Thu, 13 Nov 2025 13:04:11 +0100
Subject: [PATCH 07/13] 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 <[email protected]>
Date: Thu, 13 Nov 2025 16:40:17 +0100
Subject: [PATCH 08/13] 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 <[email protected]>
Date: Tue, 18 Nov 2025 11:02:49 +0100
Subject: [PATCH 09/13] 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 <[email protected]>
Date: Thu, 27 Nov 2025 15:13:55 +0100
Subject: [PATCH 10/13] 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 <[email protected]>
Date: Fri, 28 Nov 2025 09:06:40 +0100
Subject: [PATCH 11/13] 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 4117ba177cd26042552a1e63355592c95163692e Mon Sep 17 00:00:00 2001
From: Dimitri Ratz <[email protected]>
Date: Fri, 9 Jan 2026 10:30:46 +0100
Subject: [PATCH 12/13] 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 <[email protected]>
Date: Mon, 12 Jan 2026 09:49:33 +0100
Subject: [PATCH 13/13] 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

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to