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 1/9] [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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 53c9892985d8094ca580a642445d6096634d3f44 Mon Sep 17 00:00:00 2001 From: Dimitri Ratz <[email protected]> Date: Tue, 18 Nov 2025 11:02:49 +0100 Subject: [PATCH 9/9] Fix code format. --- clang-tools-extra/clangd/Protocol.h | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) 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 {}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
