[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