Skip to content

[clangd] Support symbolTags for document symbol #113669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions clang-tools-extra/clangd/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,90 @@ bool isImplementationDetail(const Decl *D) {
D->getASTContext().getSourceManager());
}

// Whether T is const in a loose sense - is a variable with this type readonly?
bool isConst(QualType T) {
if (T.isNull())
return false;
T = T.getNonReferenceType();
if (T.isConstQualified())
return true;
if (const auto *AT = T->getAsArrayTypeUnsafe())
return isConst(AT->getElementType());
if (isConst(T->getPointeeType()))
return true;
return false;
}

bool isConst(const Decl *D) {
if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
return true;
if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
if (isConst(llvm::cast<ValueDecl>(D)->getType()))
return true;
}
if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
if (OCPD->isReadOnly())
return true;
}
if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
if (!MPD->hasSetter())
return true;
}
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
if (CMD->isConst())
return true;
}
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
return isConst(FD->getReturnType());
return false;
}

bool isStatic(const Decl *D) {
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isStatic();
if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
return VD->isStaticDataMember() || VD->isStaticLocal();
if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
return OPD->isClassProperty();
if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
return OMD->isClassMethod();
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
return FD->isStatic();
return false;
}

bool isAbstract(const Decl *D) {
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isPureVirtual();
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
return CRD->hasDefinition() && CRD->isAbstract();
return false;
}

bool isVirtual(const Decl *D) {
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isVirtual();
return false;
}

bool 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 `-`.
Expand Down
31 changes: 31 additions & 0 deletions clang-tools-extra/clangd/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,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);
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'
Expand Down
36 changes: 36 additions & 0 deletions clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,41 @@ 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 (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;
Expand Down Expand Up @@ -241,6 +276,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;
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,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);
}
Expand Down
29 changes: 27 additions & 2 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,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
Expand All @@ -1107,6 +1131,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
Expand Down Expand Up @@ -1558,8 +1585,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 {};
Expand Down
103 changes: 1 addition & 102 deletions clang-tools-extra/clangd/SemanticHighlighting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "SemanticHighlighting.h"
#include "AST.h"
#include "Config.h"
#include "FindTarget.h"
#include "HeuristicResolver.h"
Expand Down Expand Up @@ -77,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,
Expand Down Expand Up @@ -192,91 +176,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;
Expand Down