Skip to content

Commit 1a7762c

Browse files
ratzdichouzzDimitri Ratz
authored andcommitted
[clangd] Add support for additional symbol tags proposed for LSP 3.18 (llvm#167536)
Implements support for symbol tags proposed for LSP 3.18 in microsoft/language-server-protocol#2003, in the `documentSymbols` and `workspace/symbols` requests. Fixes clangd/clangd#2123. --------- Co-authored-by: chouzz <zhouhua258@outlook.com> Co-authored-by: Dimitri Ratz <dimitri.ratz@thinkdigital.cc>
1 parent 10868eb commit 1a7762c

File tree

8 files changed

+468
-145
lines changed

8 files changed

+468
-145
lines changed

clang-tools-extra/clangd/FindSymbols.cpp

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,211 @@
2323
#include "llvm/ADT/StringRef.h"
2424
#include <limits>
2525
#include <optional>
26-
#include <tuple>
2726

2827
#define DEBUG_TYPE "FindSymbols"
2928

3029
namespace clang {
3130
namespace clangd {
3231

32+
namespace {
33+
34+
// "Static" means many things in C++, only some get the "static" modifier.
35+
//
36+
// Meanings that do:
37+
// - Members associated with the class rather than the instance.
38+
// This is what 'static' most often means across languages.
39+
// - static local variables
40+
// These are similarly "detached from their context" by the static keyword.
41+
// In practice, these are rarely used inside classes, reducing confusion.
42+
//
43+
// Meanings that don't:
44+
// - Namespace-scoped variables, which have static storage class.
45+
// This is implicit, so the keyword "static" isn't so strongly associated.
46+
// If we want a modifier for these, "global scope" is probably the concept.
47+
// - Namespace-scoped variables/functions explicitly marked "static".
48+
// There the keyword changes *linkage* , which is a totally different concept.
49+
// If we want to model this, "file scope" would be a nice modifier.
50+
//
51+
// This is confusing, and maybe we should use another name, but because "static"
52+
// is a standard LSP modifier, having one with that name has advantages.
53+
bool isStatic(const Decl *D) {
54+
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
55+
return CMD->isStatic();
56+
if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
57+
return VD->isStaticDataMember() || VD->isStaticLocal();
58+
if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
59+
return OPD->isClassProperty();
60+
if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
61+
return OMD->isClassMethod();
62+
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
63+
return FD->isStatic();
64+
return false;
65+
}
66+
67+
// Whether T is const in a loose sense - is a variable with this type readonly?
68+
bool isConst(QualType T) {
69+
if (T.isNull())
70+
return false;
71+
T = T.getNonReferenceType();
72+
if (T.isConstQualified())
73+
return true;
74+
if (const auto *AT = T->getAsArrayTypeUnsafe())
75+
return isConst(AT->getElementType());
76+
if (isConst(T->getPointeeType()))
77+
return true;
78+
return false;
79+
}
80+
81+
// Whether D is const in a loose sense (should it be highlighted as such?)
82+
// FIXME: This is separate from whether *a particular usage* can mutate D.
83+
// We may want V in V.size() to be readonly even if V is mutable.
84+
bool isConst(const Decl *D) {
85+
if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
86+
return true;
87+
if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
88+
llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
89+
if (isConst(llvm::cast<ValueDecl>(D)->getType()))
90+
return true;
91+
}
92+
if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
93+
if (OCPD->isReadOnly())
94+
return true;
95+
}
96+
if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
97+
if (!MPD->hasSetter())
98+
return true;
99+
}
100+
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
101+
if (CMD->isConst())
102+
return true;
103+
}
104+
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
105+
return isConst(FD->getReturnType());
106+
return false;
107+
}
108+
109+
// Indicates whether declaration D is abstract in cases where D is a struct or a
110+
// class.
111+
bool isAbstract(const Decl *D) {
112+
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
113+
return CMD->isPureVirtual();
114+
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
115+
return CRD->hasDefinition() && CRD->isAbstract();
116+
return false;
117+
}
118+
119+
// Indicates whether declaration D is virtual in cases where D is a method.
120+
bool isVirtual(const Decl *D) {
121+
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
122+
return CMD->isVirtual();
123+
return false;
124+
}
125+
126+
// Indicates whether declaration D is final in cases where D is a struct, class
127+
// or method.
128+
bool isFinal(const Decl *D) {
129+
if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
130+
return CRD->hasAttr<FinalAttr>();
131+
132+
if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
133+
return CRD->hasAttr<FinalAttr>();
134+
135+
return false;
136+
}
137+
138+
// Indicates whether declaration D is a unique definition (as opposed to a
139+
// declaration).
140+
bool isUniqueDefinition(const NamedDecl *Decl) {
141+
if (auto *Func = dyn_cast<FunctionDecl>(Decl))
142+
return Func->isThisDeclarationADefinition();
143+
if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
144+
return Klass->isThisDeclarationADefinition();
145+
if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
146+
return Iface->isThisDeclarationADefinition();
147+
if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
148+
return Proto->isThisDeclarationADefinition();
149+
if (auto *Var = dyn_cast<VarDecl>(Decl))
150+
return Var->isThisDeclarationADefinition();
151+
return isa<TemplateTypeParmDecl>(Decl) ||
152+
isa<NonTypeTemplateParmDecl>(Decl) ||
153+
isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
154+
isa<ObjCImplDecl>(Decl);
155+
}
156+
} // namespace
157+
158+
SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
159+
return (1 << static_cast<unsigned>(ST));
160+
}
161+
162+
SymbolTags computeSymbolTags(const NamedDecl &ND) {
163+
SymbolTags Result = 0;
164+
const auto IsDef = isUniqueDefinition(&ND);
165+
166+
if (ND.isDeprecated())
167+
Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
168+
169+
if (isConst(&ND))
170+
Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
171+
172+
if (isStatic(&ND))
173+
Result |= toSymbolTagBitmask(SymbolTag::Static);
174+
175+
if (isVirtual(&ND))
176+
Result |= toSymbolTagBitmask(SymbolTag::Virtual);
177+
178+
if (isAbstract(&ND))
179+
Result |= toSymbolTagBitmask(SymbolTag::Abstract);
180+
181+
if (isFinal(&ND))
182+
Result |= toSymbolTagBitmask(SymbolTag::Final);
183+
184+
if (not isa<UnresolvedUsingValueDecl>(ND)) {
185+
// Do not treat an UnresolvedUsingValueDecl as a declaration.
186+
// It's more common to think of it as a reference to the
187+
// underlying declaration.
188+
Result |= toSymbolTagBitmask(SymbolTag::Declaration);
189+
190+
if (IsDef)
191+
Result |= toSymbolTagBitmask(SymbolTag::Definition);
192+
}
193+
194+
switch (ND.getAccess()) {
195+
case AS_public:
196+
Result |= toSymbolTagBitmask(SymbolTag::Public);
197+
break;
198+
case AS_protected:
199+
Result |= toSymbolTagBitmask(SymbolTag::Protected);
200+
break;
201+
case AS_private:
202+
Result |= toSymbolTagBitmask(SymbolTag::Private);
203+
break;
204+
default:
205+
break;
206+
}
207+
208+
return Result;
209+
}
210+
211+
std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND) {
212+
const auto symbolTags = computeSymbolTags(ND);
213+
std::vector<SymbolTag> Tags;
214+
215+
if (symbolTags == 0)
216+
return Tags;
217+
218+
// Iterate through SymbolTag enum values and collect any that are present in
219+
// the bitmask. SymbolTag values are in the numeric range
220+
// [FirstTag .. LastTag].
221+
constexpr unsigned MinTag = static_cast<unsigned>(SymbolTag::FirstTag);
222+
constexpr unsigned MaxTag = static_cast<unsigned>(SymbolTag::LastTag);
223+
for (unsigned I = MinTag; I <= MaxTag; ++I) {
224+
auto ST = static_cast<SymbolTag>(I);
225+
if (symbolTags & toSymbolTagBitmask(ST))
226+
Tags.push_back(ST);
227+
}
228+
return Tags;
229+
}
230+
33231
namespace {
34232
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
35233
struct ScoredSymbolGreater {
@@ -242,6 +440,7 @@ std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
242440
SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()),
243441
sourceLocToPosition(SM, SymbolRange->getEnd())};
244442
SI.detail = getSymbolDetail(Ctx, ND);
443+
SI.tags = getSymbolTags(ND);
245444

246445
SourceLocation NameLoc = ND.getLocation();
247446
SourceLocation FallbackNameLoc;

clang-tools-extra/clangd/FindSymbols.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,23 @@
1414

1515
#include "Protocol.h"
1616
#include "index/Symbol.h"
17+
#include "clang/AST/Decl.h"
1718
#include "llvm/ADT/StringRef.h"
1819

1920
namespace clang {
2021
namespace clangd {
2122
class ParsedAST;
2223
class SymbolIndex;
2324

25+
/// A bitmask type representing symbol tags supported by LSP.
26+
/// \see
27+
/// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#symbolTag
28+
using SymbolTags = uint32_t;
29+
/// Ensure we have enough bits to represent all SymbolTag values.
30+
static_assert(static_cast<unsigned>(SymbolTag::LastTag) <= 32,
31+
"Too many SymbolTags to fit in uint32_t. Change to uint64_t if "
32+
"we ever have more than 32 tags.");
33+
2434
/// Helper function for deriving an LSP Location from an index SymbolLocation.
2535
llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,
2636
llvm::StringRef TUPath);
@@ -47,6 +57,18 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
4757
/// same order that they appear.
4858
llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST);
4959

60+
/// Converts a single SymbolTag to a bitmask.
61+
SymbolTags toSymbolTagBitmask(SymbolTag ST);
62+
63+
/// Computes symbol tags for a given NamedDecl.
64+
SymbolTags computeSymbolTags(const NamedDecl &ND);
65+
66+
/// Returns the symbol tags for the given declaration.
67+
/// This is a wrapper around computeSymbolTags() which unpacks
68+
/// the tags into a vector.
69+
/// \p ND The declaration to get tags for.
70+
std::vector<SymbolTag> getSymbolTags(const NamedDecl &ND);
71+
5072
} // namespace clangd
5173
} // namespace clang
5274

clang-tools-extra/clangd/Protocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,8 @@ llvm::json::Value toJSON(const DocumentSymbol &S) {
964964
Result["children"] = S.children;
965965
if (S.deprecated)
966966
Result["deprecated"] = true;
967+
if (!S.tags.empty())
968+
Result["tags"] = S.tags;
967969
// FIXME: workaround for older gcc/clang
968970
return std::move(Result);
969971
}

clang-tools-extra/clangd/Protocol.h

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ struct TextDocumentEdit {
281281
/// The text document to change.
282282
VersionedTextDocumentIdentifier textDocument;
283283

284-
/// The edits to be applied.
284+
/// The edits to be applied.
285285
/// FIXME: support the AnnotatedTextEdit variant.
286286
std::vector<TextEdit> edits;
287287
};
@@ -560,7 +560,7 @@ struct ClientCapabilities {
560560

561561
/// The client supports versioned document changes for WorkspaceEdit.
562562
bool DocumentChanges = false;
563-
563+
564564
/// The client supports change annotations on text edits,
565565
bool ChangeAnnotation = false;
566566

@@ -1027,12 +1027,12 @@ struct WorkspaceEdit {
10271027
/// Versioned document edits.
10281028
///
10291029
/// If a client neither supports `documentChanges` nor
1030-
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
1031-
/// using the `changes` property are supported.
1030+
/// `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
1031+
/// using the `changes` property are supported.
10321032
std::optional<std::vector<TextDocumentEdit>> documentChanges;
1033-
1033+
10341034
/// A map of change annotations that can be referenced in
1035-
/// AnnotatedTextEdit.
1035+
/// AnnotatedTextEdit.
10361036
std::map<std::string, ChangeAnnotation> changeAnnotations;
10371037
};
10381038
bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path);
@@ -1104,6 +1104,35 @@ struct CodeAction {
11041104
};
11051105
llvm::json::Value toJSON(const CodeAction &);
11061106

1107+
/// Symbol tags are extra annotations that can be attached to a symbol.
1108+
/// \see https://github.com/microsoft/language-server-protocol/pull/2003
1109+
enum class SymbolTag {
1110+
Deprecated = 1,
1111+
Private = 2,
1112+
Package = 3,
1113+
Protected = 4,
1114+
Public = 5,
1115+
Internal = 6,
1116+
File = 7,
1117+
Static = 8,
1118+
Abstract = 9,
1119+
Final = 10,
1120+
Sealed = 11,
1121+
Transient = 12,
1122+
Volatile = 13,
1123+
Synchronized = 14,
1124+
Virtual = 15,
1125+
Nullable = 16,
1126+
NonNull = 17,
1127+
Declaration = 18,
1128+
Definition = 19,
1129+
ReadOnly = 20,
1130+
1131+
// Update as needed
1132+
FirstTag = Deprecated,
1133+
LastTag = ReadOnly
1134+
};
1135+
llvm::json::Value toJSON(SymbolTag);
11071136
/// Represents programming constructs like variables, classes, interfaces etc.
11081137
/// that appear in a document. Document symbols can be hierarchical and they
11091138
/// have two ranges: one that encloses its definition and one that points to its
@@ -1121,6 +1150,9 @@ struct DocumentSymbol {
11211150
/// Indicates if this symbol is deprecated.
11221151
bool deprecated = false;
11231152

1153+
/// The tags for this symbol.
1154+
std::vector<SymbolTag> tags;
1155+
11241156
/// The range enclosing this symbol not including leading/trailing whitespace
11251157
/// but everything else like comments. This information is typically used to
11261158
/// determine if the clients cursor is inside the symbol to reveal in the
@@ -1146,6 +1178,9 @@ struct SymbolInformation {
11461178
/// The kind of this symbol.
11471179
SymbolKind kind;
11481180

1181+
/// Tags for this symbol, e.g public, private, static, const etc.
1182+
std::vector<SymbolTag> tags;
1183+
11491184
/// The location of this symbol.
11501185
Location location;
11511186

@@ -1288,13 +1323,13 @@ enum class InsertTextFormat {
12881323
/// Additional details for a completion item label.
12891324
struct CompletionItemLabelDetails {
12901325
/// An optional string which is rendered less prominently directly after label
1291-
/// without any spacing. Should be used for function signatures or type
1326+
/// without any spacing. Should be used for function signatures or type
12921327
/// annotations.
12931328
std::string detail;
12941329

12951330
/// An optional string which is rendered less prominently after
1296-
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
1297-
/// names or file path.
1331+
/// CompletionItemLabelDetails.detail. Should be used for fully qualified
1332+
/// names or file path.
12981333
std::string description;
12991334
};
13001335
llvm::json::Value toJSON(const CompletionItemLabelDetails &);
@@ -1572,9 +1607,6 @@ struct ResolveTypeHierarchyItemParams {
15721607
bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &,
15731608
llvm::json::Path);
15741609

1575-
enum class SymbolTag { Deprecated = 1 };
1576-
llvm::json::Value toJSON(SymbolTag);
1577-
15781610
/// The parameter of a `textDocument/prepareCallHierarchy` request.
15791611
struct CallHierarchyPrepareParams : public TextDocumentPositionParams {};
15801612

0 commit comments

Comments
 (0)