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 @@ -1134,6 +1138,58 @@ HI.Provider = spellHeader(AST, SM.getFileEntryForID(SM.getMainFileID()), H); } +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, @@ -1163,6 +1219,7 @@ HI.Definition = URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str(); HI.DefinitionLanguage = ""; + maybeAddUsedSymbols(AST, HI, Inc); return HI; } @@ -1378,6 +1435,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 @@ -124,27 +124,6 @@ return true; } -std::vector -collectMacroReferences(ParsedAST &AST) { - const auto &SM = AST.getSourceManager(); - // FIXME: !!this is a hacky way to collect macro references. - std::vector Macros; - auto &PP = AST.getPreprocessor(); - for (const syntax::Token &Tok : - AST.getTokens().spelledTokens(SM.getMainFileID())) { - auto Macro = locateMacroAt(Tok, PP); - if (!Macro) - continue; - if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) - Macros.push_back( - {Tok.location(), - include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), - DefLoc}, - include_cleaner::RefType::Explicit}); - } - return Macros; -} - llvm::StringRef getResolvedPath(const include_cleaner::Header &SymProvider) { switch (SymProvider.kind()) { case include_cleaner::Header::Physical: @@ -276,6 +255,28 @@ } } // namespace + +std::vector +collectMacroReferences(ParsedAST &AST) { + const auto &SM = AST.getSourceManager(); + // FIXME: !!this is a hacky way to collect macro references. + std::vector Macros; + auto &PP = AST.getPreprocessor(); + for (const syntax::Token &Tok : + AST.getTokens().spelledTokens(SM.getMainFileID())) { + auto Macro = locateMacroAt(Tok, PP); + if (!Macro) + continue; + if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid()) + Macros.push_back( + {Tok.location(), + include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)), + DefLoc}, + include_cleaner::RefType::Explicit}); + } + return Macros; +} + include_cleaner::Includes convertIncludes(const SourceManager &SM, const llvm::ArrayRef MainFileIncludes) { 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 @@ -2951,7 +2953,7 @@ TU.AdditionalFiles["all.h"] = guard(R"cpp( #include "foo.h" )cpp"); - + auto AST = TU.build(); auto H = getHover(AST, Case.Code.point(), format::getLLVMStyle(), nullptr); ASSERT_TRUE(H); @@ -2980,6 +2982,74 @@ EXPECT_EQ(Case.HI.present().asMarkdown(), Case.ExpectedMarkdown); } +TEST(Hover, UsedSymbols) { + struct { + Annotations Code; + const std::function ExpectedBuilder; + } Cases[] = {{Annotations(R"cpp( + [[#inclu^de "foo.h"]] + + int var = foo(); + )cpp"), + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[#incl^ude "foo.h"]] + )cpp"), + [](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"), + [](HoverInfo &HI) { + HI.UsedSymbolNames = {"X", "bar", "foobar"}; + }}, + {Annotations(R"cpp( + [[#in^clude ]] + + int fooVar = foo(); + )cpp"), + [](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }}, + {Annotations(R"cpp( + [[#in^clude "public.h"]] + #include "private.h" + + int fooVar = 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("int foo();"); + TU.AdditionalFiles["bar.h"] = guard("int bar(); int foobar(); class X {};"); + TU.AdditionalFiles["private.h"] = guard(R"cpp( + // IWYU pragma: private, include "public.h" + int foo(); + )cpp"); + TU.AdditionalFiles["public.h"] = guard(""); + TU.AdditionalFiles["system/foo"] = guard("int foo();"); + 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( template class X {};