Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clangd] Show definition of underlying struct when hovering over a typedef #89570

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion clang-tools-extra/clangd/Hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought: what do you think of resolving pointer types as well? I could envision cases where pointers are involved in an alias type e.g.

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

(excerpted from PROCESS_INFORMATION)
People may want to peek at the definition of the struct while hovering over LPPROCESS_INFORMATION too.

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)) {
Expand All @@ -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;
}
Expand Down
102 changes: 100 additions & 2 deletions clang-tools-extra/clangd/unittests/HoverTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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";
}},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Loading