================
@@ -23,13 +23,217 @@
#include "llvm/ADT/StringRef.h"
#include <limits>
#include <optional>
-#include <tuple>
#define DEBUG_TYPE "FindSymbols"
namespace clang {
namespace clangd {
+namespace {
+SymbolTags toSymbolTagBitmask(const SymbolTag ST) {
+ return (1 << static_cast<unsigned>(ST));
+}
+} // namespace
+
+
+
+// "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();
+ if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+ return FD->isStatic();
+ return false;
+}
+
+// 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;
+ }
+ if (const auto *FD = llvm::dyn_cast<FunctionDecl>(D))
+ return isConst(FD->getReturnType());
+ return false;
+}
+
+// Indicates whether declaration D is abstract in cases where D is a struct or
a
+// class.
+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;
+}
+
+// Indicates whether declaration D is virtual in cases where D is a method.
+bool isVirtual(const Decl *D) {
+ if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
+ return CMD->isVirtual();
+ return false;
+}
+
+// Indicates whether declaration D is final in cases where D is a struct, class
+// or method.
+bool isFinal(const Decl *D) {
+ if (const auto *CRD = dyn_cast<CXXMethodDecl>(D))
+ return CRD->hasAttr<FinalAttr>();
+
+ if (const auto *CRD = dyn_cast<CXXRecordDecl>(D))
+ return CRD->hasAttr<FinalAttr>();
+
+ return false;
+}
+
+
+bool isUniqueDefinition(const NamedDecl *Decl) {
+ if (auto *Func = dyn_cast<FunctionDecl>(Decl))
+ return Func->isThisDeclarationADefinition();
+ if (auto *Klass = dyn_cast<CXXRecordDecl>(Decl))
+ return Klass->isThisDeclarationADefinition();
+ if (auto *Iface = dyn_cast<ObjCInterfaceDecl>(Decl))
+ return Iface->isThisDeclarationADefinition();
+ if (auto *Proto = dyn_cast<ObjCProtocolDecl>(Decl))
+ return Proto->isThisDeclarationADefinition();
+ if (auto *Var = dyn_cast<VarDecl>(Decl))
+ return Var->isThisDeclarationADefinition();
+ return isa<TemplateTypeParmDecl>(Decl) ||
+ isa<NonTypeTemplateParmDecl>(Decl) ||
+ isa<TemplateTemplateParmDecl>(Decl) || isa<ObjCCategoryDecl>(Decl) ||
+ isa<ObjCImplDecl>(Decl);
+}
+
+// Backwards-compatible default behavior: determine whether this NamedDecl is
+// definition based on `isUniqueDefinition`, assuming that ND is a declaration.
+SymbolTags computeSymbolTags(const NamedDecl &ND) {
+ return computeSymbolTags(ND, true);
+}
+
+SymbolTags computeSymbolTags(const NamedDecl &ND, bool IsDecl) {
+ SymbolTags Result = 0;
+ const auto IsDef = isUniqueDefinition(&ND);
+
+ if (ND.isDeprecated())
+ Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
+
+ if (isConst(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::ReadOnly);
+
+ if (isStatic(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Static);
+
+ if (isVirtual(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Virtual);
+
+ if (isAbstract(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Abstract);
+
+ if (isFinal(&ND))
+ Result |= toSymbolTagBitmask(SymbolTag::Final);
+
+ if (IsDecl && not isa<UnresolvedUsingValueDecl>(ND)) {
+ // Do not treat an UnresolvedUsingValueDecl as a declaration.
+ // It's more common to think of it as a reference to the
+ // underlying declaration.
+ Result |= toSymbolTagBitmask(SymbolTag::Declaration);
----------------
ratzdi wrote:
In` SemanticHighlighting.cpp` (main branch Line 1132) there is an if-statement
that I have kept (otherwise breaking tests) in my work (without precise
knowledge of the purpose of the condition)
```c++
if (R.IsDecl) {
// Do not treat an UnresolvedUsingValueDecl as a declaration.
// It's more common to think of it as a reference to the
// underlying declaration.
if (!isa<UnresolvedUsingValueDecl>(Decl))
Tok.addModifier(HighlightingModifier::Declaration);
if (isUniqueDefinition(Decl))
Tok.addModifier(HighlightingModifier::Definition);
}
```
now the if-statement is located in computeSymbolTags
`const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl);`
and the lines
```c++
...
const auto SymbolTags = computeSymbolTags(*Decl, R.IsDecl);
...
if (SymbolTags & toSymbolTagBitmask(SymbolTag::Declaration))
Tok.addModifier(HighlightingModifier::Declaration);
if (SymbolTags & toSymbolTagBitmask(SymbolTag::Definition))
Tok.addModifier(HighlightingModifier::Definition);
...
```
replacing the code block, mentioned above.
> The [wording in the
> PR](https://github.com/microsoft/language-server-protocol/pull/2003/changes#diff-9403073ad34251b3ce4b6442973eb6d78dd5b61aeda57a12a0a8126be7c2fadcR269-R279)
> -- specifically "Render a symbol as definition (in contrast to declaration)"
> -- makes me think perhaps Declaration should only be set for things that are
> not a Definition?
When we decide to either ... or, some tests will break.
https://github.com/llvm/llvm-project/pull/167536
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits