diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h --- a/clang-tools-extra/clangd/Hover.h +++ b/clang-tools-extra/clangd/Hover.h @@ -15,6 +15,7 @@ #include "clang/Index/IndexSymbol.h" #include #include +#include namespace clang { namespace clangd { @@ -110,6 +111,9 @@ }; // Set only if CalleeArgInfo is set. std::optional CallPassType; + // Set when hovering over the #include line. Contains the names of symbols + // from a #include'd file that are used in the main file. + std::optional> UsedSymbolNames; /// Produce a user-readable information. markup::Document present() const; diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -12,6 +12,7 @@ #include "CodeCompletionStrings.h" #include "Config.h" #include "FindTarget.h" +#include "Headers.h" #include "IncludeCleaner.h" #include "ParsedAST.h" #include "Selection.h" @@ -38,7 +39,9 @@ #include "clang/AST/RecordLayout.h" #include "clang/AST/Type.h" #include "clang/Basic/CharInfo.h" +#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Basic/TokenKinds.h" #include "clang/Index/IndexSymbol.h" @@ -53,6 +56,7 @@ #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/raw_ostream.h" #include +#include #include #include @@ -1126,6 +1130,58 @@ } } +std::string getRefName(include_cleaner::SymbolReference Ref) { + std::string Name; + switch (Ref.Target.kind()) { + case include_cleaner::Symbol::Declaration: + Name = llvm::dyn_cast(&Ref.Target.declaration()) + ->getDeclName() + .getAsString(); + break; + case include_cleaner::Symbol::Macro: + Name = Ref.Target.macro().Name->getName(); + break; + } + return Name; +} + +void maybeAddUsedSymbols(ParsedAST &AST, HoverInfo &HI, const Inclusion &Inc) { + const SourceManager &SM = AST.getSourceManager(); + std::set UsedSymbolNames; + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), collectMacroReferences(AST), + AST.getPragmaIncludes(), SM, + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + + if (Ref.RT != include_cleaner::RefType::Explicit || + !SM.isWrittenInMainFile(SM.getSpellingLoc(Ref.RefLocation))) { + return; + } + + for (const include_cleaner::Header &H : Providers) { + switch (H.kind()) { + case include_cleaner::Header::Physical: + if (Inc.Resolved == H.physical()->tryGetRealPathName()) + UsedSymbolNames.insert(getRefName(Ref)); + break; + case include_cleaner::Header::Standard: + if (Inc.Written == H.standard().name()) + UsedSymbolNames.insert(getRefName(Ref)); + break; + case include_cleaner::Header::Verbatim: + if (Inc.Written == H.verbatim()) + UsedSymbolNames.insert(getRefName(Ref)); + break; + } + } + }); + + if (!UsedSymbolNames.empty()) + HI.UsedSymbolNames = std::optional>{ + {UsedSymbolNames.begin(), UsedSymbolNames.end()}}; +} + } // namespace std::optional getHover(ParsedAST &AST, Position Pos, @@ -1155,6 +1211,7 @@ HI.Definition = URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str(); HI.DefinitionLanguage = ""; + maybeAddUsedSymbols(AST, HI, Inc); return HI; } @@ -1370,6 +1427,24 @@ Output.addCodeBlock(Buffer, DefinitionLanguage); } + if (UsedSymbolNames) { + Output.addRuler(); + markup::Paragraph &P = Output.addParagraph(); + P.appendText("provides "); + + for (unsigned I = 0; I < UsedSymbolNames->size() - 1 && I < 4; I++) { + P.appendCode((*UsedSymbolNames)[I]); + P.appendText(", "); + } + + P.appendCode((*UsedSymbolNames)[UsedSymbolNames->size() - 1]); + if (UsedSymbolNames->size() > 5) { + P.appendText(" and "); + P.appendText(std::to_string(UsedSymbolNames->size() - 5)); + P.appendText(" more"); + } + } + return Output; } diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -74,6 +74,9 @@ std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile, include_cleaner::Header Provider); + +std::vector +collectMacroReferences(ParsedAST &AST); } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -253,6 +253,7 @@ } return Result; } +} // namespace std::vector collectMacroReferences(ParsedAST &AST) { @@ -274,7 +275,6 @@ } return Macros; } -} // namespace include_cleaner::Includes convertIncludes(const SourceManager &SM, diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -10,6 +10,7 @@ #include "Annotations.h" #include "Config.h" #include "Hover.h" +#include "TestFS.h" #include "TestIndex.h" #include "TestTU.h" #include "index/MemIndex.h" @@ -20,6 +21,7 @@ #include "gtest/gtest.h" #include +#include #include #include @@ -2941,7 +2943,7 @@ )cpp"), "int foo();", "#include \"foo.h\"", "#include \"foo.h\"", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, - {Annotations( + {Annotations( R"cpp( #include "foo.h" @@ -2954,8 +2956,9 @@ private: int val; }; - )cpp", "", "", [](HoverInfo &HI) { HI.Provider = ""; }}, - {Annotations( + )cpp", + "", "", [](HoverInfo &HI) { HI.Provider = ""; }}, + {Annotations( R"cpp( #include "bar.h" @@ -2963,20 +2966,23 @@ )cpp"), R"cpp( int foo(); - )cpp", "#include \"foo.h\"", "", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, - {Annotations( + )cpp", + "#include \"foo.h\"", "", + [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, + {Annotations( R"cpp( #include "bar.h" int F = [[f^oo]](); )cpp"), "int foo();", - R"cpp( + R"cpp( #include "foo.h" #include "foobar.h" - )cpp", "int foo();", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, - {Annotations( + )cpp", + "int foo();", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, + {Annotations( R"cpp( #include "bar.h" @@ -2984,18 +2990,19 @@ )cpp"), R"cpp( #include "foobar.h" - )cpp", R"cpp( + )cpp", + R"cpp( #include "foo.h" #include "foobar.h" - )cpp", "int foo();", [](HoverInfo &HI) { HI.Provider = "\"foobar.h\""; }}, + )cpp", + "int foo();", [](HoverInfo &HI) { HI.Provider = "\"foobar.h\""; }}, {Annotations( R"cpp( #include "foo.h" int F = [[^MACRO]]; )cpp"), - "#define MACRO 5", - "", "", + "#define MACRO 5", "", "", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, {Annotations( R"cpp( @@ -3004,8 +3011,7 @@ int F = [[M^ACRO]]; )cpp"), - "#define MACRO 5", - "#define MACRO 10", "", + "#define MACRO 5", "#define MACRO 10", "", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, {Annotations( R"cpp( @@ -3014,16 +3020,14 @@ int F = [[MA^CRO]](5); )cpp"), - "#define MACRO(X) X+1", - "#include \"foobar.h\"", "#define MACRO(X) X+3", + "#define MACRO(X) X+1", "#include \"foobar.h\"", "#define MACRO(X) X+3", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, {Annotations( R"cpp( #include "foo.h" int [[^F]] = MACRO(5); )cpp"), - "#define MACRO(X) (X+1)", - "", "", + "#define MACRO(X) (X+1)", "", "", [](HoverInfo &HI) { HI.Provider = ""; }}, {Annotations( R"cpp( @@ -3032,7 +3036,7 @@ )cpp"), "#define MACRO 5", "#include \"foo.h\"", "", [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}, - {Annotations( + {Annotations( R"cpp( #include "bar.h" int F = [[MACR^O]]; @@ -3040,9 +3044,9 @@ "#define MACRO 5", R"cpp( #include "foo.h" #include "foobar.h" - )cpp", "#define MACRO 10", - [](HoverInfo &HI) { HI.Provider = "\"foobar.h\""; }}, - {Annotations( + )cpp", + "#define MACRO 10", [](HoverInfo &HI) { HI.Provider = "\"foobar.h\""; }}, + {Annotations( R"cpp( #include "bar.h" @@ -3051,21 +3055,21 @@ "", R"cpp( // IWYU pragma: private, include "foo.h" int foo(); - )cpp", "", - [](HoverInfo &HI) { HI.Provider = "\"bar.h\""; }}, - {Annotations( + )cpp", + "", [](HoverInfo &HI) { HI.Provider = "\"bar.h\""; }}, + {Annotations( R"cpp( #include "bar.h" int F = [[fo^o]](); )cpp"), "", "#include \"foobar.h\"", - R"cpp( + R"cpp( // IWYU pragma: private, include "public.h" int foo(); )cpp", [](HoverInfo &HI) { HI.Provider = "\"public.h\""; }}, - {Annotations( + {Annotations( R"cpp( #include "bar.h" @@ -3075,10 +3079,10 @@ namespace std { class vector {}; } - )cpp", "#include \"foo.h\"", - "", + )cpp", + "#include \"foo.h\"", "", [](HoverInfo &HI) { HI.Provider = ""; }}, - {Annotations( + {Annotations( R"cpp( #include "foo.h" @@ -3090,10 +3094,9 @@ R"cpp( class Foo {}; Foo& operator+(const Foo &lhs, const Foo &rhs); - )cpp", "#include \"foo.h\"", - "", - [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }} - }; + )cpp", + "#include \"foo.h\"", "", + [](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }}}; for (const auto &Case : Cases) { SCOPED_TRACE(Case.Code.code()); @@ -3143,7 +3146,7 @@ namespace absl { using std::string_view; })cpp"; - llvm::StringLiteral StdHeader = R"cpp( + llvm::StringLiteral StdHeader = R"cpp( namespace std { class string_view {}; })cpp"; @@ -3160,6 +3163,104 @@ EXPECT_EQ(H->Provider, ""); } +TEST(Hover, UsedSymbols) { + struct { + Annotations Code; + llvm::StringLiteral FooHeader; + llvm::StringLiteral BarHeader; + llvm::StringLiteral SystemHeader; + const std::function ExpectedBuilder; + } Cases[] = {{Annotations(R"cpp( + [[#inclu^de "foo.h"]] + + int var = foo(); + )cpp"), + "int foo();", "", "", + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[^#include "foo.h"]] + + int var = foo(); + )cpp"), + "int foo();", "", "", + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[#include "fo^o.h"]] + + int var = foo(); + )cpp"), + "int foo();", "", "", + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[#include "foo.h"^]] + + int var = foo(); + )cpp"), + "int foo();", "", "", + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + #include "foo.h" + [[#incl^ude "bar.h"]] + + int var = foo(); + )cpp"), + "int foo();", "int bar();", "", + [](HoverInfo &HI) { HI.UsedSymbolNames = {}; }}, + {Annotations(R"cpp( + #include "foo.h" + [[#include ^"bar.h"]] + + int fooVar = foo(); + int barVar = bar(); + int foobarVar = foobar(); + + X x; + )cpp"), + "int foo();", "int bar(); int foobar(); class X {};", "", + [](HoverInfo &HI) { + HI.UsedSymbolNames = {"X", "bar", "foobar"}; + }}, + {Annotations(R"cpp( + [[#in^clude ]] + + int fooVar = foo(); + )cpp"), + "", "", + R"cpp( + int foo(); + )cpp", + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[#in^clude "bar.h"]] + #include "foo.h" + + int fooVar = foo(); + )cpp"), + R"cpp( + // IWYU pragma: private, include "bar.h" + int foo(); + )cpp", + "", "", [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}}; + for (const auto &Case : Cases) { + SCOPED_TRACE(Case.Code.code()); + + TestTU TU; + TU.Filename = "foo.cpp"; + TU.Code = Case.Code.code(); + TU.AdditionalFiles["foo.h"] = guard(Case.FooHeader); + TU.AdditionalFiles["bar.h"] = guard(Case.BarHeader); + TU.AdditionalFiles["system/foo"] = guard(Case.SystemHeader); + TU.ExtraArgs.push_back("-isystem" + testPath("system")); + + auto AST = TU.build(); + auto H = getHover(AST, Case.Code.point(), format::getLLVMStyle(), nullptr); + ASSERT_TRUE(H); + HoverInfo Expected; + Case.ExpectedBuilder(Expected); + SCOPED_TRACE(H->present().asMarkdown()); + EXPECT_EQ(H->UsedSymbolNames, Expected.UsedSymbolNames); + } +} TEST(Hover, DocsFromIndex) { Annotations T(R"cpp(