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 @@ -67,6 +67,8 @@ std::string LocalScope; /// Name of the symbol, does not contain any "::". std::string Name; + /// Headers providing the symbol (directly included in the main file). + std::optional> ProviderInfo; std::optional SymRange; index::SymbolKind Kind = index::SymbolKind::Unknown; std::string Documentation; 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 @@ -15,8 +15,12 @@ #include "ParsedAST.h" #include "Selection.h" #include "SourceCode.h" +#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Types.h" #include "index/SymbolCollector.h" +#include "support/Logger.h" #include "support/Markup.h" +#include "support/Trace.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTTypeTraits.h" @@ -1084,6 +1088,100 @@ return Candidates.front(); } +// FIXME(bakalova): Remove after merging https://reviews.llvm.org/D143496 +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; +} + +void maybeAddSymbolProviders(ParsedAST &AST, HoverInfo &HI, + SourceLocation CurLoc) { + trace::Span Tracer("Hover::maybeAddSymbolProviders"); + include_cleaner::walkUsed( + AST.getLocalTopLevelDecls(), collectMacroReferences(AST), + AST.getPragmaIncludes(), AST.getSourceManager(), + [&](const include_cleaner::SymbolReference &Ref, + llvm::ArrayRef Providers) { + const SourceManager &SM = AST.getSourceManager(); + if (!Ref.RefLocation.isFileID() || + !SM.isWrittenInMainFile(SM.getSpellingLoc(Ref.RefLocation))) + // Ignore locations within macro expansions or not in the main file + return; + + if (Ref.RT != include_cleaner::RefType::Explicit) + // Ignore non-explicit references (e.g. variable names). + return; + + const syntax::Token *Tok = + AST.getTokens().spelledTokenAt(Ref.RefLocation); + if (Tok == nullptr) { + elog("Can't find spelled token at the symbol reference location."); + return; + } + const SourceLocation &RefEndLocation = Tok->endLocation(); + + // Check that the user is hovering over the current symbol reference. + if (CurLoc < Ref.RefLocation || CurLoc > RefEndLocation) { + return; + } + if (Providers.empty()) + return; + + std::vector DirectlyIncludedProviders; + std::vector MainFileIncludes = + AST.getIncludeStructure().MainFileIncludes; + for (const Inclusion &Inc : MainFileIncludes) { + for (const include_cleaner::Header &Provider : Providers) { + if (Provider.kind() == include_cleaner::Header::Physical && + SM.getFileEntryForID(SM.getMainFileID()) == + Provider.physical()) { + // Do not report main file as provider. + continue; + } + + llvm::StringRef WrittenRef = + llvm::StringRef(Inc.Written).trim("\"<>"); + + switch (Provider.kind()) { + case include_cleaner::Header::Physical: + if (Provider.physical()->tryGetRealPathName() == Inc.Resolved) + DirectlyIncludedProviders.push_back(WrittenRef.str()); + break; + case include_cleaner::Header::Standard: + if (Provider.standard().name() == Inc.Written) + DirectlyIncludedProviders.push_back(WrittenRef.str()); + break; + case include_cleaner::Header::Verbatim: + if (Provider.verbatim() == Inc.Written) + DirectlyIncludedProviders.push_back(WrittenRef.str()); + } + } + } + + if (DirectlyIncludedProviders.empty()) + return; + + HI.ProviderInfo = std::optional>{ + std::move(DirectlyIncludedProviders)}; + }); +} + } // namespace std::optional getHover(ParsedAST &AST, Position Pos, @@ -1131,6 +1229,8 @@ HighlightRange = Tok.range(SM).toCharRange(SM); if (auto M = locateMacroAt(Tok, AST.getPreprocessor())) { HI = getHoverContents(*M, Tok, AST); + if (HI) + maybeAddSymbolProviders(AST, *HI, *CurLoc); break; } } else if (Tok.kind() == tok::kw_auto || Tok.kind() == tok::kw_decltype) { @@ -1165,9 +1265,10 @@ if (DeclToUse == N->ASTNode.get()) addLayoutInfo(*DeclToUse, *HI); // Look for a close enclosing expression to show the value of. - if (!HI->Value) + if (HI && !HI->Value) HI->Value = printExprValue(N, AST.getASTContext()); maybeAddCalleeArgInfo(N, *HI, PP); + maybeAddSymbolProviders(AST, *HI, *CurLoc); } else if (const Expr *E = N->ASTNode.get()) { HI = getHoverContents(N, E, AST, PP, Index); } else if (const Attr *A = N->ASTNode.get()) { @@ -1231,6 +1332,20 @@ llvm::to_string(*ReturnType)); } + if (ProviderInfo) { + Output.addRuler(); + markup::Paragraph &P = Output.addParagraph(); + P.appendText("Provided by "); + P.appendSpace(); + for (unsigned I = 0; I < (*ProviderInfo).size() - 1; I++) { + P.appendCode((*ProviderInfo)[I]); + P.appendText(","); + P.appendSpace(); + } + P.appendCode((*ProviderInfo).back()); + Output.addRuler(); + } + if (Parameters && !Parameters->empty()) { Output.addParagraph().appendText("Parameters: "); markup::BulletList &L = Output.addBulletList(); 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 @@ -28,6 +28,10 @@ using PassMode = HoverInfo::PassType::PassMode; +std::string guard(llvm::StringRef Code) { + return "#pragma once\n" + Code.str(); +} + TEST(Hover, Structured) { struct { const char *const Code; @@ -2882,6 +2886,160 @@ } } +TEST(Hover, Providers) { + struct { + Annotations Code; + llvm::StringLiteral FooHeader; + llvm::StringLiteral BarHeader; + llvm::StringLiteral FooBarHeader; + const std::function ExpectedBuilder; + } Cases[] = {{Annotations( + R"cpp( + #include "foo.h" + int F = [[fo^o]](); + )cpp"), + "int foo();", "", "", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "bar.h" + #include "foo.h" + int F = [[f^oo]](); + )cpp"), + "int foo();", "int foo();", "", + [](HoverInfo &HI) { + HI.ProviderInfo = {"bar.h", "foo.h"}; + }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[^foo]](); + )cpp"), + "#include \"bar.h\"", "int foo();", "", + [](HoverInfo &HI) { HI.ProviderInfo = {}; }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[^foo]](); + )cpp"), + R"cpp( + #include "bar.h" + int foo(); + )cpp", + "int foo();", "", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "bar.h" + #include "foo.h" + #include "foobar.h" + int F = [[^foo]](); + )cpp"), + "int foo();", "int foo();", "#include \"bar.h\"", + [](HoverInfo &HI) { + HI.ProviderInfo = {"bar.h", "foo.h"}; + }}, + {Annotations( + R"cpp( + #include "bar.h" + #include "foo.h" + #include "foobar.h" + int F = [[foo^]](); + )cpp"), + "int foo();", "#include \"foo.h\"", "#include \"foo.h\"", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[^MACRO]]; + )cpp"), + R"cpp( + #include "bar.h" + #define MACRO 5 + )cpp", + "#define MACRO 10", "", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[M^ACRO]](5); + )cpp"), + R"cpp( + #include "bar.h" + #define MACRO(X) (X+1) + )cpp", + "#define MACRO 10", "", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[MA^CRO]](5); + )cpp"), + R"cpp( + #include "bar.h" + #include "foobar.h" + #define MACRO(X) MACRO_HELPER(X) + )cpp", + "#define MACRO_HELPER(X) (X+1)", "#define MACRO(X) (X+10)", + [](HoverInfo &HI) { HI.ProviderInfo = {"foo.h"}; }}, + {Annotations( + R"cpp( + #include "foo.h" + int F = [[MAC^RO]](5); + )cpp"), + R"cpp( + #include "bar.h" + #include "foobar.h" + )cpp", + "#define MACRO(X) (X+1)", "#define MACRO(X) (X+2)", + [](HoverInfo &HI) { HI.ProviderInfo = {}; }}, + {Annotations( + R"cpp( + #include "foo.h" + #include "bar.h" + int F = [[MACR^O]]; + )cpp"), + "#define MACRO 5", "#define MACRO 10", "#define MACRO 15", + [](HoverInfo &HI) { HI.ProviderInfo = {"bar.h"}; }}}; + + 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["foobar.h"] = guard(Case.FooBarHeader); + + 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->ProviderInfo, Expected.ProviderInfo); + } +} + +TEST(Hover, ParseProviderInfo) { + HoverInfo HIFoo; + HIFoo.Name = "foo"; + HIFoo.ProviderInfo = {"foo.h"}; + + HoverInfo HIFooBar; + HIFooBar.Name = "foo"; + HIFooBar.ProviderInfo = {"bar.h", "foo.h"}; + struct Case { + HoverInfo HI; + llvm::StringRef ExpectedMarkdown; + } Cases[] = {{HIFoo, "### `foo` \n\n---\nProvided by `foo.h`"}, + {HIFooBar, "### `foo` \n\n---\nProvided by `bar.h`, `foo.h`"}}; + + for (const auto &Case : Cases) + EXPECT_EQ(Case.HI.present().asMarkdown(), Case.ExpectedMarkdown); +} + TEST(Hover, DocsFromIndex) { Annotations T(R"cpp( template class X {};