diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h index 4371c80a6c587..3b6658e7ff034 100644 --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -136,6 +136,9 @@ struct Config { struct { /// Whether hover show a.k.a type. bool ShowAKA = true; + + /// Whether to show struct fields + bool ShowFields = false; } Hover; struct { diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index 5bb2eb4a9f803..714a04fc77a5f 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -616,6 +616,12 @@ struct FragmentCompiler { C.Hover.ShowAKA = ShowAKA; }); } + if (F.ShowFields) { + Out.Apply.push_back( + [ShowFields(**F.ShowFields)](const Params &, Config &C) { + C.Hover.ShowFields = ShowFields; + }); + } } void compile(Fragment::InlayHintsBlock &&F) { diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 7fa61108c78a0..ccbc412f4fc53 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -309,8 +309,11 @@ struct Fragment { /// Describes hover preferences. struct HoverBlock { - /// Whether hover show a.k.a type. + /// Whether hovers show a.k.a type. std::optional> ShowAKA; + + /// Whether hovers show struct fields + std::optional> ShowFields; }; HoverBlock Hover; diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index ce09af819247a..68e4d66763d16 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -235,6 +235,10 @@ class Parser { if (auto ShowAKA = boolValue(N, "ShowAKA")) F.ShowAKA = *ShowAKA; }); + Dict.handle("ShowFields", [&](Node &N) { + if (auto ShowFields = boolValue(N, "ShowFields")) + F.ShowFields = *ShowFields; + }); Dict.parse(N); } diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 06b949bc4a2b5..db4049402f8fc 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -66,12 +66,53 @@ namespace clang { namespace clangd { namespace { +// A PrintingCallbacks implementation that prints public fields of +// record types and enum members. Meant to be used with TerseOutput=true. +class ClangdPrintingCallbacks : public PrintingCallbacks { +public: + virtual ~ClangdPrintingCallbacks() = default; + + void summarizeTagDecl(raw_ostream &Out, const TagDecl *D, + const PrintingPolicy &PP) const override { + if (!Config::current().Hover.ShowFields) { + Out << " {}"; + return; + } + + // Don't worry about the details of the whitespace, e.g. space vs. newline, + // as the printed definition will be passed through clang-format before + // being sent to the client. + Out << " { "; + const bool IsRecord = isa(D); + for (DeclContext::decl_iterator Member = D->decls_begin(), + MemberEnd = D->decls_end(); + Member != MemberEnd; ++Member) { + if (isa(*Member) || + (isa(*Member) && + dyn_cast(*Member)->getAccess() == AS_public)) { + Member->print(Out, PP, 1); + if (IsRecord) { + Out << "; "; + } else { + DeclContext::decl_iterator Next = Member; + ++Next; + if (Next != MemberEnd) + Out << ", "; + } + } + } + Out << "}"; + } +}; + PrintingPolicy getPrintingPolicy(PrintingPolicy Base) { + static ClangdPrintingCallbacks Callbacks; Base.AnonymousTagLocations = false; Base.TerseOutput = true; Base.PolishForDeclaration = true; Base.ConstantsAsWritten = true; Base.SuppressTemplateArgsInCXXConstructors = true; + Base.Callbacks = &Callbacks; return Base; } diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp index 44a6647d4c0a8..42cca3c619760 100644 --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -230,6 +230,19 @@ TEST(ParseYAML, ShowAKA) { EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true))); } +TEST(ParseYAML, ShowFields) { + CapturedDiags Diags; + Annotations YAML(R"yaml( +Hover: + ShowFields: True + )yaml"); + auto Results = + Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_EQ(Results.size(), 1u); + EXPECT_THAT(Results[0].Hover.ShowFields, llvm::ValueIs(val(true))); +} + TEST(ParseYAML, InlayHints) { CapturedDiags Diags; Annotations YAML(R"yaml( diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 35db757b9c15b..13d3993a592be 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -1645,7 +1645,11 @@ TEST(Hover, All) { { R"cpp(// Struct namespace ns1 { - struct MyClass {}; + struct MyClass { + // Fields not shown without ShowFields=true + int field1; + int field2; + }; } // namespace ns1 int main() { ns1::[[My^Class]]* Params; @@ -3941,6 +3945,157 @@ TEST(Hover, DisableShowAKA) { EXPECT_EQ(H->Type, HoverInfo::PrintedType("m_int")); } +TEST(Hover, ShowFields) { + struct { + const char *const Code; + const std::function ExpectedBuilder; + } Cases[] = { + { + R"cpp(// Struct + namespace ns1 { + struct MyClass { + // Public fields shown in hover + int field1; + int field2; + + // Methods and private fields not shown + void method(); + private: + bool private_field; + }; + } // namespace ns1 + int main() { + ns1::[[My^Class]]* Params; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "struct MyClass {\n int field1;\n int field2;\n}"; + } + }, + { + R"cpp(// Union + namespace ns1 { + union MyUnion { int x; int y; }; + } // namespace ns1 + int main() { + ns1::[[My^Union]] Params; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "union MyUnion {\n int x;\n int y;\n}"; + } + }, + { + R"cpp(// Enum declaration + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + [[Hel^lo]] hello = ONE; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "enum Hello { ONE, TWO, THREE }"; + } + }, + }; + for (const auto &Case : Cases) { + SCOPED_TRACE(Case.Code); + + Annotations T(Case.Code); + TestTU TU = TestTU::withCode(T.code()); + TU.ExtraArgs.push_back("-std=c++20"); + TU.ExtraArgs.push_back("-xobjective-c++"); + + // 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(); + Config Cfg; + Cfg.Hover.ShowFields = true; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + 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->Definition, Expected.Definition); + } +} + +TEST(Hover, ShowFields_CLanguage) { + struct { + const char *const Code; + const std::function ExpectedBuilder; + } Cases[] = { + { + R"cpp(// Struct + struct MyStruct { + // Public fields shown in hover + int field1; + int field2; + }; + int main() { + struct [[My^Struct]]* Params; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "struct MyStruct {\n int field1;\n int field2;\n}"; + } + }, + { + R"cpp(// Union + union MyUnion { int x; int y; }; + int main() { + union [[My^Union]] Params; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "union MyUnion {\n int x;\n int y;\n}"; + } + }, + { + R"cpp(// Enum declaration + enum Hello { + ONE, TWO, THREE, + }; + void foo() { + enum [[Hel^lo]] hello = ONE; + } + )cpp", + [](HoverInfo &HI) { + HI.Definition = "enum Hello { ONE, TWO, THREE }"; + } + }, + }; + 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(); + Config Cfg; + Cfg.Hover.ShowFields = true; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + 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->Definition, Expected.Definition); + } +} + TEST(Hover, HideBigInitializers) { Annotations T(R"cpp( #define A(x) x, x, x, x diff --git a/clang/include/clang/AST/PrettyPrinter.h b/clang/include/clang/AST/PrettyPrinter.h index da276e26049b0..7e8ab32ceb5d7 100644 --- a/clang/include/clang/AST/PrettyPrinter.h +++ b/clang/include/clang/AST/PrettyPrinter.h @@ -21,6 +21,7 @@ namespace clang { class DeclContext; class LangOptions; class Stmt; +class TagDecl; class PrinterHelper { public: @@ -29,6 +30,9 @@ class PrinterHelper { }; /// Callbacks to use to customize the behavior of the pretty-printer. + +struct PrintingPolicy; + class PrintingCallbacks { protected: ~PrintingCallbacks() = default; @@ -47,6 +51,13 @@ class PrintingCallbacks { /// The printing stops at the first isScopeVisible() == true, so there will /// be no calls with outer scopes. virtual bool isScopeVisible(const DeclContext *DC) const { return false; } + + /// Print a summary of the body of a TagDecl when PrintingPolicy::TerseOutput + /// is true. + virtual void summarizeTagDecl(raw_ostream &Out, const TagDecl *D, + const PrintingPolicy &PP) const { + Out << " {}"; + } }; /// Describes how types, statements, expressions, and declarations should be @@ -249,6 +260,8 @@ struct PrintingPolicy { /// For example, in this mode we don't print function bodies, class members, /// declarations inside namespaces etc. Effectively, this should print /// only the requested declaration. + /// The behaviour for printing the bodies of record and enum decls can be + /// customized by overriding PrintingCallbacks::summarizeTagDecl. LLVM_PREFERRED_TYPE(bool) unsigned TerseOutput : 1; diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 43d221968ea3f..27679eb87e127 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -21,6 +21,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/PrettyPrinter.h" #include "clang/Basic/Module.h" +#include "clang/Basic/Specifiers.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -631,9 +632,16 @@ void DeclPrinter::VisitEnumDecl(EnumDecl *D) { Out << " : " << D->getIntegerType().stream(Policy); if (D->isCompleteDefinition()) { - Out << " {\n"; - VisitDeclContext(D); - Indent() << "}"; + if (Policy.TerseOutput) { + if (Policy.Callbacks) + Policy.Callbacks->summarizeTagDecl(Out, D, Policy); + else + Out << " {}"; + } else { + Out << " {\n"; + VisitDeclContext(D); + Indent() << "}"; + } } } @@ -648,9 +656,16 @@ void DeclPrinter::VisitRecordDecl(RecordDecl *D) { Out << ' ' << *D; if (D->isCompleteDefinition()) { - Out << " {\n"; - VisitDeclContext(D); - Indent() << "}"; + if (Policy.TerseOutput) { + if (Policy.Callbacks) + Policy.Callbacks->summarizeTagDecl(Out, D, Policy); + else + Out << " {}"; + } else { + Out << " {\n"; + VisitDeclContext(D); + Indent() << "}"; + } } } @@ -1183,7 +1198,10 @@ void DeclPrinter::VisitCXXRecordDecl(CXXRecordDecl *D) { // Print the class definition // FIXME: Doesn't print access specifiers, e.g., "public:" if (Policy.TerseOutput) { - Out << " {}"; + if (Policy.Callbacks) + Policy.Callbacks->summarizeTagDecl(Out, D, Policy); + else + Out << " {}"; } else { Out << " {\n"; VisitDeclContext(D); diff --git a/clang/unittests/AST/DeclPrinterTest.cpp b/clang/unittests/AST/DeclPrinterTest.cpp index 0e09ab2a7bba8..60a92ec77a0f8 100644 --- a/clang/unittests/AST/DeclPrinterTest.cpp +++ b/clang/unittests/AST/DeclPrinterTest.cpp @@ -20,6 +20,7 @@ #include "ASTPrint.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" @@ -78,12 +79,13 @@ PrintedDeclCXX98Matches(StringRef Code, const DeclarationMatcher &NodeMatch, PolicyModifier); } -::testing::AssertionResult PrintedDeclCXX11Matches(StringRef Code, - StringRef DeclName, - StringRef ExpectedPrinted) { +::testing::AssertionResult +PrintedDeclCXX11Matches(StringRef Code, StringRef DeclName, + StringRef ExpectedPrinted, + PrintingPolicyAdjuster PolicyModifier = nullptr) { std::vector Args(1, "-std=c++11"); return PrintedDeclMatches(Code, Args, namedDecl(hasName(DeclName)).bind("id"), - ExpectedPrinted, "input.cc"); + ExpectedPrinted, "input.cc", PolicyModifier); } ::testing::AssertionResult PrintedDeclCXX11Matches( @@ -1430,3 +1432,27 @@ TEST(DeclPrinter, VarDeclWithInitializer) { PrintedDeclCXX17Matches("void foo() {int arr[42]; for(int a : arr);}", namedDecl(hasName("a")).bind("id"), "int a")); } + +TEST(DeclPrinter, SummarizeTagDecl) { + class TestPrintingCallbacks : public PrintingCallbacks { + public: + virtual ~TestPrintingCallbacks() = default; + virtual void summarizeTagDecl(raw_ostream &Out, const TagDecl *D, + const PrintingPolicy &PP) const { + Out << " { Custom Tag Decl Summary }"; + } + }; + TestPrintingCallbacks Callbacks; + auto UseCallbacks = [&](PrintingPolicy &Policy) { + Policy.Callbacks = &Callbacks; + }; + ASSERT_TRUE(PrintedDeclCXX11Matches( + "struct MyStruct { int x; int y; };", "MyStruct", + "struct MyStruct { Custom Tag Decl Summary }", UseCallbacks)); + ASSERT_TRUE(PrintedDeclCXX11Matches( + "union MyUnion { int x; int y; };", "MyUnion", + "union MyUnion { Custom Tag Decl Summary }", UseCallbacks)); + ASSERT_TRUE(PrintedDeclCXX11Matches( + "enum MyEnum { one, two, three };", "MyEnum", + "enum MyEnum { Custom Tag Decl Summary }", UseCallbacks)); +}