From 28d564bcc9e2e12b812e3fd3ff38b85bffec5c91 Mon Sep 17 00:00:00 2001 From: Nathan Ridge Date: Thu, 19 Feb 2026 03:31:38 -0500 Subject: [PATCH] [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 --- clang-tools-extra/clangd/FindSymbols.cpp | 4 +- clang-tools-extra/clangd/Protocol.cpp | 16 +++++-- clang-tools-extra/clangd/Protocol.h | 2 +- clang-tools-extra/clangd/XRefs.cpp | 4 +- .../clangd/unittests/FindSymbolsTests.cpp | 22 ++++++++++ clang/include/clang/Index/IndexSymbol.h | 2 + clang/lib/Index/IndexSymbol.cpp | 43 ++++++++++++++++--- clang/test/Index/Core/index-source.cpp | 4 +- 8 files changed, 80 insertions(+), 17 deletions(-) diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index 243746056aed..e3d4b7febaf1 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -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 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); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index a697486d48f9..793db7b05299 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -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: diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index a88c9a391f97..7a99721a1e85 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -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 { diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index d4398a593d48..5b9ba1baa070 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -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 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). diff --git a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp index d0dd5d0f8f43..2d237429ebfb 100644 --- a/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp @@ -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( diff --git a/clang/include/clang/Index/IndexSymbol.h b/clang/include/clang/Index/IndexSymbol.h index deb9337d9d1a..4a7f523b885f 100644 --- a/clang/include/clang/Index/IndexSymbol.h +++ b/clang/include/clang/Index/IndexSymbol.h @@ -79,6 +79,8 @@ enum class SymbolSubKind : uint8_t { UsingTypename, UsingValue, UsingEnum, + UsingClass, + UsingStruct, }; typedef uint16_t SymbolPropertySet; diff --git a/clang/lib/Index/IndexSymbol.cpp b/clang/lib/Index/IndexSymbol.cpp index c3cbc03cf9b4..9b7fee421bdf 100644 --- a/clang/lib/Index/IndexSymbol.cpp +++ b/clang/lib/Index/IndexSymbol.cpp @@ -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(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(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"); } diff --git a/clang/test/Index/Core/index-source.cpp b/clang/test/Index/Core/index-source.cpp index 36bc663b8968..e376523a58f7 100644 --- a/clang/test/Index/Core/index-source.cpp +++ b/clang/test/Index/Core/index-source.cpp @@ -23,10 +23,10 @@ class Cls { public: // CHECK: [[@LINE+2]]:24 | class/C++ | Cls | [[Cls_USR]] | | 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:.*]] | | Def | rel: 0 +// CHECK: [[@LINE+1]]:13 | type-alias/using-class/C | ClsAlias | [[ClsAlias_USR:.*]] | | Def | rel: 0 typedef Cls ClsAlias; // CHECK: [[@LINE+5]]:7 | class/C++ | SubCls2 | [[SubCls2_USR:.*]] | | Def | rel: 0 -// CHECK: [[@LINE+4]]:24 | type-alias/C | ClsAlias | [[ClsAlias_USR]] | | Ref,RelCont | rel: 1 +// CHECK: [[@LINE+4]]:24 | type-alias/using-class/C | ClsAlias | [[ClsAlias_USR]] | | Ref,RelCont | rel: 1 // CHECK-NEXT: RelCont | SubCls2 | [[SubCls2_USR]] // CHECK: [[@LINE+2]]:24 | class/C++ | Cls | [[Cls_USR]] | | Ref,Impl,RelBase,RelCont | rel: 1 // CHECK-NEXT: RelBase,RelCont | SubCls2 | [[SubCls2_USR]]