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 @@ -1172,20 +1172,16 @@ UsedSymbols.find(Ref.Target) != UsedSymbols.end()) return; - for (const include_cleaner::Header &H : Providers) { - auto MatchingIncludes = ConvertedMainFileIncludes.match(H); - // No match for this provider in the main file. - if (MatchingIncludes.empty()) - continue; - - // Check if the hovered include matches this provider. - if (!HoveredInclude.match(H).empty()) - UsedSymbols.insert(Ref.Target); - - // Don't look for rest of the providers once we've found a match - // in the main file. - break; - } + auto Provider = + firstSatisfiedProvider(ConvertedMainFileIncludes, Providers); + if (!Provider) + return; + + // Check if the hovered include matches this provider. + if (HoveredInclude.match(*Provider).empty()) + return; + + UsedSymbols.insert(Ref.Target); }); for (const auto &UsedSymbolDecl : UsedSymbols) 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 @@ -81,6 +81,11 @@ std::vector collectMacroReferences(ParsedAST &AST); + +/// Find the first provider in the list that is matched by the includes. +std::optional +firstSatisfiedProvider(const include_cleaner::Includes &Includes, + llvm::ArrayRef Providers); } // 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 @@ -432,5 +432,15 @@ return Result; } +std::optional +firstSatisfiedProvider(const include_cleaner::Includes &Includes, + llvm::ArrayRef Providers) { + for (const auto &H : Providers) { + if (!Includes.match(H).empty()) + return H; + } + // No match for this provider in the includes list. + return {}; +} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -10,12 +10,15 @@ #include "FindSymbols.h" #include "FindTarget.h" #include "HeuristicResolver.h" +#include "IncludeCleaner.h" #include "ParsedAST.h" #include "Protocol.h" #include "Quality.h" #include "Selection.h" #include "SourceCode.h" #include "URI.h" +#include "clang-include-cleaner/Analysis.h" +#include "clang-include-cleaner/Types.h" #include "index/Index.h" #include "index/Merge.h" #include "index/Relation.h" @@ -48,6 +51,7 @@ #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexingOptions.h" #include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" @@ -61,6 +65,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace clang { @@ -1312,6 +1317,63 @@ } } // namespace +ReferencesResult maybeFindIncludeReferences(ParsedAST &AST, Position Pos, + URIForFile URIMainFile) { + ReferencesResult Results; + const SourceManager &SM = AST.getSourceManager(); + auto Includes = AST.getIncludeStructure().MainFileIncludes; + auto ConvertedMainFileIncludes = convertIncludes(SM, Includes); + for (auto &Inc : Includes) { + if (Inc.HashLine != Pos.line) + continue; + + auto ReferencedInclude = convertIncludes(SM, Inc); + 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) + return; + + auto Provider = + firstSatisfiedProvider(ConvertedMainFileIncludes, Providers); + if (!Provider) + return; + + // Check if the referenced include matches this provider. + if (ReferencedInclude.match(*Provider).empty()) + return; + + auto Loc = SM.getFileLoc(Ref.RefLocation); + // File locations can be outside of the main file if macro is + // expanded through an #include. + while (SM.getFileID(Loc) != SM.getMainFileID()) + Loc = SM.getIncludeLoc(SM.getFileID(Loc)); + + ReferencesResult::Reference Result; + const auto *Token = AST.getTokens().spelledTokenAt(Loc); + Result.Loc.range = + Range{sourceLocToPosition(SM, Token->location()), + sourceLocToPosition(SM, Token->endLocation())}; + Result.Loc.uri = URIMainFile; + Results.References.push_back(std::move(Result)); + }); + if (Results.References.empty()) + return {}; + + // Add the #include line to the references list. + auto IncludeLen = + std::string{"#include"}.length() + Inc.Written.length() + 1; + ReferencesResult::Reference Result; + Result.Loc.range = clangd::Range{Position{Inc.HashLine, 0}, + Position{Inc.HashLine, (int)IncludeLen}}; + Result.Loc.uri = URIMainFile; + Results.References.push_back(std::move(Result)); + } + return Results; +} + ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext) { ReferencesResult Results; @@ -1324,6 +1386,10 @@ return {}; } + Results = maybeFindIncludeReferences(AST, Pos, URIMainFile); + if (!Results.References.empty()) + return Results; + llvm::DenseSet IDsToQuery, OverriddenMethods; const auto *IdentifierAtCursor = diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -43,6 +43,10 @@ using ::testing::UnorderedElementsAreArray; using ::testing::UnorderedPointwise; +std::string guard(llvm::StringRef Code) { + return "#pragma once\n" + Code.str(); +} + MATCHER_P2(FileRange, File, Range, "") { return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; } @@ -2293,6 +2297,51 @@ checkFindRefs(Test); } +TEST(FindReferences, UsedSymbolsFromInclude) { + const char *Tests[] = { + R"cpp([[#include ^"bar.h"]] + #include + int fstBar = [[bar1]](); + int sndBar = [[bar2]](); + [[Bar]] bar; + int macroBar = [[BAR]]; + std::vector vec; + )cpp", + + R"cpp([[#in^clude ]] + std::[[vector]] vec; + )cpp"}; + for (const char *Test : Tests) { + Annotations T(Test); + auto TU = TestTU::withCode(T.code()); + TU.ExtraArgs.push_back("-std=c++20"); + TU.AdditionalFiles["bar.h"] = guard(R"cpp( + #define BAR 5 + int bar1(); + int bar2(); + class Bar {}; + )cpp"); + TU.AdditionalFiles["system/vector"] = guard(R"cpp( + namespace std { + template + class vector{}; + } + )cpp"); + TU.ExtraArgs.push_back("-isystem" + testPath("system")); + + auto AST = TU.build(); + std::vector> ExpectedLocations; + for (const auto &R : T.ranges()) + ExpectedLocations.push_back(AllOf(rangeIs(R), attrsAre(0u))); + for (const auto &P : T.points()) { + EXPECT_THAT(findReferences(AST, P, 0).References, + UnorderedElementsAreArray(ExpectedLocations)) + << "Failed for Refs at " << P << "\n" + << Test; + } + } +} + TEST(FindReferences, NeedsIndexForSymbols) { const char *Header = "int foo();"; Annotations Main("int main() { [[f^oo]](); }");