diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 06b949bc4a2b5..864c73f379c88 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(T.getTypePtr())) { + D = TT->getDecl(); + } else if (const auto *TT = dyn_cast(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(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(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 35db757b9c15b..582a311f35658 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 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"); + EXPECT_EQ(H->Definition, "using foo = type;\nusing type = int"); } TEST(Hover, EvaluateMacros) {