[clang-tools-extra] Symbol tags in SymbolInformation, WorkspaceSymbol, CallHierarchyItem and TypeHierarchyItem (PR #170103)
Dimitri Ratz via cfe-commits
cfe-commits at lists.llvm.org
Mon Dec 15 00:58:08 PST 2025
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/19] [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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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());
};
More information about the cfe-commits
mailing list