[clang-tools-extra] [clangd] Show struct members when hovering over a typedef (PR #89570)
Nathan Ridge via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 25 15:56:22 PDT 2024
https://github.com/HighCommander4 updated https://github.com/llvm/llvm-project/pull/89570
>From 44aba390954c7b551ed7102e8e7b4209207c0d87 Mon Sep 17 00:00:00 2001
From: Nathan Ridge <zeratul976 at hotmail.com>
Date: Mon, 22 Apr 2024 02:24:14 -0400
Subject: [PATCH] [clangd] Show definition of underlying struct when hovering
over a typedef
Fixes https://github.com/clangd/clangd/issues/2020
---
clang-tools-extra/clangd/Hover.cpp | 43 +++++++-
.../clangd/unittests/HoverTests.cpp | 102 +++++++++++++++++-
2 files changed, 142 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 06b949bc4a2b55..864c73f379c884 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -136,6 +136,41 @@ std::string getNamespaceScope(const Decl *D) {
return "";
}
+void printDeclAndWrappers(const TypedefNameDecl *TND,
+ llvm::raw_string_ostream &OS, PrintingPolicy PP) {
+ TND->print(OS, PP);
+ const Decl *LastPrintedDecl = TND;
+
+ auto PrintDeclForType = [&](QualType T) {
+ Decl *D = nullptr;
+ if (const auto *TT = dyn_cast<TagType>(T.getTypePtr())) {
+ D = TT->getDecl();
+ } else if (const auto *TT = dyn_cast<TypedefType>(T.getTypePtr())) {
+ D = TT->getDecl();
+ }
+ if (D == LastPrintedDecl) {
+ return false;
+ }
+ if (D) {
+ OS << ";\n";
+ D->print(OS, PP);
+ LastPrintedDecl = D;
+ }
+ // In case of D == nullptr, return true. We might have a layer of type
+ // sugar like ElaboratedType that doesn't itself have a distinct Decl,
+ // but a subsequent layer of type sugar might.
+ return true;
+ };
+
+ QualType Type = TND->getUnderlyingType();
+ while (PrintDeclForType(Type)) {
+ QualType Desugared = Type->getLocallyUnqualifiedSingleStepDesugaredType();
+ if (Desugared == Type)
+ break;
+ Type = Desugared;
+ }
+}
+
std::string printDefinition(const Decl *D, PrintingPolicy PP,
const syntax::TokenBuffer &TB) {
if (auto *VD = llvm::dyn_cast<VarDecl>(D)) {
@@ -149,7 +184,13 @@ std::string printDefinition(const Decl *D, PrintingPolicy PP,
}
std::string Definition;
llvm::raw_string_ostream OS(Definition);
- D->print(OS, PP);
+
+ if (const auto *TND = dyn_cast<TypedefNameDecl>(D)) {
+ printDeclAndWrappers(TND, OS, PP);
+ } else {
+ D->print(OS, PP);
+ }
+
OS.flush();
return Definition;
}
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 35db757b9c15b5..582a311f35658d 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -1657,6 +1657,35 @@ TEST(Hover, All) {
HI.NamespaceScope = "ns1::";
HI.Definition = "struct MyClass {}";
}},
+ {
+ R"cpp(// Typedef to struct
+ struct Point { int x; int y; };
+ typedef Point TPoint;
+ [[TP^oint]] tp;
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "TPoint";
+ HI.Kind = index::SymbolKind::TypeAlias;
+ HI.NamespaceScope = "";
+ HI.Type = "struct Point";
+ HI.Definition = "typedef Point TPoint;\nstruct Point {}";
+ }
+ },
+ {
+ R"cpp(// Two layers of typedef
+ struct Point { int x; int y; };
+ typedef Point TPoint;
+ typedef TPoint TTPoint;
+ [[TTP^oint]] tp;
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "TTPoint";
+ HI.Kind = index::SymbolKind::TypeAlias;
+ HI.NamespaceScope = "";
+ HI.Type = "struct Point";
+ HI.Definition = "typedef TPoint TTPoint;\ntypedef Point TPoint;\nstruct Point {}";
+ }
+ },
{
R"cpp(// Class
namespace ns1 {
@@ -1880,7 +1909,7 @@ TEST(Hover, All) {
HI.Name = "Foo";
HI.Kind = index::SymbolKind::TypeAlias;
HI.NamespaceScope = "";
- HI.Definition = "typedef struct Bar Foo";
+ HI.Definition = "typedef struct Bar Foo;\nstruct Bar {}";
HI.Type = "struct Bar";
HI.Documentation = "Typedef with embedded definition";
}},
@@ -3124,6 +3153,75 @@ TEST(Hover, All) {
}
}
+TEST(Hover, CLanguage) {
+ struct {
+ const char *const Code;
+ const std::function<void(HoverInfo &)> ExpectedBuilder;
+ } Cases[] = {
+ {
+ R"cpp(// Typedef to struct
+ struct Point { int x; int y; };
+ typedef struct Point TPoint;
+ [[TP^oint]] tp;
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "TPoint";
+ HI.Kind = index::SymbolKind::TypeAlias;
+ HI.NamespaceScope = "";
+ HI.Type = "struct Point";
+ HI.Definition = "typedef struct Point TPoint;\nstruct Point {}";
+ }
+ },
+ {
+ R"cpp(// Two layers of typedef
+ struct Point { int x; int y; };
+ typedef struct Point TPoint;
+ typedef TPoint TTPoint;
+ [[TTP^oint]] tp;
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "TTPoint";
+ HI.Kind = index::SymbolKind::TypeAlias;
+ HI.NamespaceScope = "";
+ HI.Type = "struct Point";
+ HI.Definition = "typedef TPoint TTPoint;\ntypedef struct Point TPoint;\nstruct Point {}";
+ }
+ },
+ };
+ for (const auto &Case : Cases) {
+ SCOPED_TRACE(Case.Code);
+
+ Annotations T(Case.Code);
+ TestTU TU = TestTU::withCode(T.code());
+ TU.ExtraArgs.push_back("-std=c99");
+ TU.ExtraArgs.push_back("-xc");
+
+ // Types might be different depending on the target triplet, we chose a
+ // fixed one to make sure tests passes on different platform.
+ TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
+ auto AST = TU.build();
+ auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+ ASSERT_TRUE(H);
+ HoverInfo Expected;
+ Expected.SymRange = T.range();
+ Case.ExpectedBuilder(Expected);
+
+ SCOPED_TRACE(H->present().asPlainText());
+ EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+ EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+ EXPECT_EQ(H->Name, Expected.Name);
+ EXPECT_EQ(H->Kind, Expected.Kind);
+ EXPECT_EQ(H->Documentation, Expected.Documentation);
+ EXPECT_EQ(H->Definition, Expected.Definition);
+ EXPECT_EQ(H->Type, Expected.Type);
+ EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+ EXPECT_EQ(H->Parameters, Expected.Parameters);
+ EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+ EXPECT_EQ(H->SymRange, Expected.SymRange);
+ EXPECT_EQ(H->Value, Expected.Value);
+ }
+}
+
TEST(Hover, Providers) {
struct {
const char *Code;
@@ -4019,7 +4117,7 @@ TEST(Hover, Typedefs) {
ASSERT_TRUE(H && H->Type);
EXPECT_EQ(H->Type->Type, "int");
- EXPECT_EQ(H->Definition, "using foo = type<true, int, double>");
+ EXPECT_EQ(H->Definition, "using foo = type<true, int, double>;\nusing type = int");
}
TEST(Hover, EvaluateMacros) {
More information about the cfe-commits
mailing list