[Index] Reflect in SymbolSubKind whether a typedef points to a struct or a class (#181967)

Typedefs don't have their own symbol kind in the Language Server
Protocol, the choices are Struct or Class. For clangd to be able to
represent typedefs accurately in response to requests such as
`workspace/symbol`, it needs this information surfaced in
index::SymbolInfo.

Fixes https://github.com/clangd/clangd/issues/2253
This commit is contained in:
Nathan Ridge 2026-02-19 03:31:38 -05:00 committed by GitHub
parent 5b9b25b9f8
commit 28d564bcc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 80 additions and 17 deletions

View File

@ -350,7 +350,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
SymbolInformation Info;
Info.name = (Sym.Name + Sym.TemplateSpecializationArgs).str();
Info.kind = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
Info.kind = indexSymbolKindToSymbolKind(Sym.SymInfo);
Info.location = *Loc;
Scope.consume_back("::");
Info.containerName = Scope.str();
@ -431,7 +431,7 @@ std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
// FIXME: This is not classifying constructors, destructors and operators
// correctly.
SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo);
DocumentSymbol SI;
SI.name = getSymbolName(Ctx, ND);

View File

@ -295,8 +295,8 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
}
}
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
switch (Kind) {
SymbolKind indexSymbolKindToSymbolKind(const index::SymbolInfo &Info) {
switch (Info.Kind) {
// FIXME: for backwards compatibility, the include directive kind is treated
// the same as Unknown
case index::SymbolKind::IncludeDirective:
@ -322,8 +322,16 @@ SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
return SymbolKind::Interface;
case index::SymbolKind::Union:
return SymbolKind::Class;
case index::SymbolKind::TypeAlias:
return SymbolKind::Class;
case index::SymbolKind::TypeAlias: {
switch (Info.SubKind) {
case index::SymbolSubKind::UsingStruct:
return SymbolKind::Struct;
case index::SymbolSubKind::UsingClass:
return SymbolKind::Class;
default:
return SymbolKind::Class;
}
}
case index::SymbolKind::Function:
return SymbolKind::Function;
case index::SymbolKind::Variable:

View File

@ -417,7 +417,7 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
// Note, some are not perfect matches and should be improved when this LSP
// issue is addressed:
// https://github.com/Microsoft/language-server-protocol/issues/344
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind);
SymbolKind indexSymbolKindToSymbolKind(const index::SymbolInfo &Info);
// Determines the encoding used to measure offsets and lengths of source in LSP.
enum class OffsetEncoding {

View File

@ -1814,7 +1814,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
// FIXME: This is not classifying constructors, destructors and operators
// correctly.
SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo);
HierarchyItem HI;
HI.name = printName(Ctx, ND);
@ -1871,7 +1871,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
HierarchyItem HI;
HI.name = std::string(S.Name);
HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
// (https://github.com/clangd/clangd/issues/59).

View File

@ -116,6 +116,28 @@ TEST(WorkspaceSymbols, Unnamed) {
withKind(SymbolKind::Field))));
}
TEST(WorkspaceSymbols, TypeAlias) {
TestTU TU;
TU.Code = R"cpp(
struct Struct {};
class Class {};
class Container {
using StructAlias = Struct;
using ClassAlias = Class;
};
)cpp";
EXPECT_THAT(
getSymbols(TU, "Struct"),
UnorderedElementsAre(AllOf(qName("Struct"), withKind(SymbolKind::Struct)),
AllOf(qName("Container::StructAlias"),
withKind(SymbolKind::Struct))));
EXPECT_THAT(
getSymbols(TU, "Class"),
UnorderedElementsAre(
AllOf(qName("Class"), withKind(SymbolKind::Class)),
AllOf(qName("Container::ClassAlias"), withKind(SymbolKind::Class))));
}
TEST(WorkspaceSymbols, InMainFile) {
TestTU TU;
TU.Code = R"cpp(

View File

@ -79,6 +79,8 @@ enum class SymbolSubKind : uint8_t {
UsingTypename,
UsingValue,
UsingEnum,
UsingClass,
UsingStruct,
};
typedef uint16_t SymbolPropertySet;

View File

@ -8,10 +8,12 @@
#include "clang/Index/IndexSymbol.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/TypeBase.h"
#include "clang/Lex/MacroInfo.h"
using namespace clang;
@ -83,6 +85,23 @@ bool index::isFunctionLocalSymbol(const Decl *D) {
return true;
}
static SymbolSubKind getSubKindForTypedef(const TypedefNameDecl *TND) {
if (const TagDecl *TD = TND->getUnderlyingType()->getAsTagDecl()) {
switch (TD->getTagKind()) {
case TagTypeKind::Class:
return SymbolSubKind::UsingClass;
case TagTypeKind::Struct:
return SymbolSubKind::UsingStruct;
default:
// Leave SymbolSubKind blank.
// New subkinds like UsingUnion can be added if/when needed.
return SymbolSubKind::None;
}
}
// Not a tag type, e.g. typedef for a builtin type.
return SymbolSubKind::None;
}
SymbolInfo index::getSymbolInfo(const Decl *D) {
assert(D);
SymbolInfo Info;
@ -171,8 +190,11 @@ SymbolInfo index::getSymbolInfo(const Decl *D) {
case Decl::Import:
Info.Kind = SymbolKind::Module;
break;
case Decl::Typedef:
Info.Kind = SymbolKind::TypeAlias; break; // Lang = C
case Decl::Typedef: {
Info.Kind = SymbolKind::TypeAlias; // Lang = C
Info.SubKind = getSubKindForTypedef(cast<TypedefDecl>(D));
break;
}
case Decl::Function:
Info.Kind = SymbolKind::Function;
break;
@ -310,10 +332,12 @@ SymbolInfo index::getSymbolInfo(const Decl *D) {
Info.Lang = SymbolLanguage::CXX;
Info.Properties |= (SymbolPropertySet)SymbolProperty::Generic;
break;
case Decl::TypeAlias:
case Decl::TypeAlias: {
Info.Kind = SymbolKind::TypeAlias;
Info.SubKind = getSubKindForTypedef(cast<TypeAliasDecl>(D));
Info.Lang = SymbolLanguage::CXX;
break;
}
case Decl::UnresolvedUsingTypename:
Info.Kind = SymbolKind::Using;
Info.SubKind = SymbolSubKind::UsingTypename;
@ -553,9 +577,16 @@ StringRef index::getSymbolSubKindString(SymbolSubKind K) {
case SymbolSubKind::CXXMoveConstructor: return "cxx-move-ctor";
case SymbolSubKind::AccessorGetter: return "acc-get";
case SymbolSubKind::AccessorSetter: return "acc-set";
case SymbolSubKind::UsingTypename: return "using-typename";
case SymbolSubKind::UsingValue: return "using-value";
case SymbolSubKind::UsingEnum: return "using-enum";
case SymbolSubKind::UsingTypename:
return "using-typename";
case SymbolSubKind::UsingValue:
return "using-value";
case SymbolSubKind::UsingEnum:
return "using-enum";
case SymbolSubKind::UsingClass:
return "using-class";
case SymbolSubKind::UsingStruct:
return "using-struct";
}
llvm_unreachable("invalid symbol subkind");
}

View File

@ -23,10 +23,10 @@ class Cls { public:
// CHECK: [[@LINE+2]]:24 | class/C++ | Cls | [[Cls_USR]] | <no-cgname> | Ref,RelBase,RelCont | rel: 1
// CHECK-NEXT: RelBase,RelCont | SubCls1 | [[SubCls1_USR]]
class SubCls1 : public Cls {};
// CHECK: [[@LINE+1]]:13 | type-alias/C | ClsAlias | [[ClsAlias_USR:.*]] | <no-cgname> | Def | rel: 0
// CHECK: [[@LINE+1]]:13 | type-alias/using-class/C | ClsAlias | [[ClsAlias_USR:.*]] | <no-cgname> | Def | rel: 0
typedef Cls ClsAlias;
// CHECK: [[@LINE+5]]:7 | class/C++ | SubCls2 | [[SubCls2_USR:.*]] | <no-cgname> | Def | rel: 0
// CHECK: [[@LINE+4]]:24 | type-alias/C | ClsAlias | [[ClsAlias_USR]] | <no-cgname> | Ref,RelCont | rel: 1
// CHECK: [[@LINE+4]]:24 | type-alias/using-class/C | ClsAlias | [[ClsAlias_USR]] | <no-cgname> | Ref,RelCont | rel: 1
// CHECK-NEXT: RelCont | SubCls2 | [[SubCls2_USR]]
// CHECK: [[@LINE+2]]:24 | class/C++ | Cls | [[Cls_USR]] | <no-cgname> | Ref,Impl,RelBase,RelCont | rel: 1
// CHECK-NEXT: RelBase,RelCont | SubCls2 | [[SubCls2_USR]]