Skip to content

Commit 9e9b1ef

Browse files
committed
[clangd] Implement cross reference request for #include lines.
Differential Revision: https://reviews.llvm.org/D147044
1 parent 357da2f commit 9e9b1ef

File tree

7 files changed

+181
-50
lines changed

7 files changed

+181
-50
lines changed

Diff for: clang-tools-extra/clangd/Hover.cpp

+6-14
Original file line numberDiff line numberDiff line change
@@ -1172,20 +1172,12 @@ void maybeAddUsedSymbols(ParsedAST &AST, HoverInfo &HI, const Inclusion &Inc) {
11721172
UsedSymbols.contains(Ref.Target))
11731173
return;
11741174

1175-
for (const include_cleaner::Header &H : Providers) {
1176-
auto MatchingIncludes = ConvertedMainFileIncludes.match(H);
1177-
// No match for this provider in the main file.
1178-
if (MatchingIncludes.empty())
1179-
continue;
1180-
1181-
// Check if the hovered include matches this provider.
1182-
if (!HoveredInclude.match(H).empty())
1183-
UsedSymbols.insert(Ref.Target);
1184-
1185-
// Don't look for rest of the providers once we've found a match
1186-
// in the main file.
1187-
break;
1188-
}
1175+
auto Provider =
1176+
firstMatchedProvider(ConvertedMainFileIncludes, Providers);
1177+
if (!Provider || HoveredInclude.match(*Provider).empty())
1178+
return;
1179+
1180+
UsedSymbols.insert(Ref.Target);
11891181
});
11901182

11911183
for (const auto &UsedSymbolDecl : UsedSymbols)

Diff for: clang-tools-extra/clangd/IncludeCleaner.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -444,5 +444,15 @@ std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
444444
return Result;
445445
}
446446

447+
std::optional<include_cleaner::Header>
448+
firstMatchedProvider(const include_cleaner::Includes &Includes,
449+
llvm::ArrayRef<include_cleaner::Header> Providers) {
450+
for (const auto &H : Providers) {
451+
if (!Includes.match(H).empty())
452+
return H;
453+
}
454+
// No match for this provider in the includes list.
455+
return std::nullopt;
456+
}
447457
} // namespace clangd
448458
} // namespace clang

Diff for: clang-tools-extra/clangd/IncludeCleaner.h

+5
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile,
8181

8282
std::vector<include_cleaner::SymbolReference>
8383
collectMacroReferences(ParsedAST &AST);
84+
85+
/// Find the first provider in the list that is matched by the includes.
86+
std::optional<include_cleaner::Header>
87+
firstMatchedProvider(const include_cleaner::Includes &Includes,
88+
llvm::ArrayRef<include_cleaner::Header> Providers);
8489
} // namespace clangd
8590
} // namespace clang
8691

Diff for: clang-tools-extra/clangd/XRefs.cpp

+68
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99
#include "AST.h"
1010
#include "FindSymbols.h"
1111
#include "FindTarget.h"
12+
#include "Headers.h"
1213
#include "HeuristicResolver.h"
14+
#include "IncludeCleaner.h"
1315
#include "ParsedAST.h"
1416
#include "Protocol.h"
1517
#include "Quality.h"
1618
#include "Selection.h"
1719
#include "SourceCode.h"
1820
#include "URI.h"
21+
#include "clang-include-cleaner/Analysis.h"
22+
#include "clang-include-cleaner/Types.h"
1923
#include "index/Index.h"
2024
#include "index/Merge.h"
2125
#include "index/Relation.h"
@@ -48,6 +52,7 @@
4852
#include "clang/Index/IndexingAction.h"
4953
#include "clang/Index/IndexingOptions.h"
5054
#include "clang/Index/USRGeneration.h"
55+
#include "clang/Lex/Lexer.h"
5156
#include "clang/Tooling/Syntax/Tokens.h"
5257
#include "llvm/ADT/ArrayRef.h"
5358
#include "llvm/ADT/DenseMap.h"
@@ -61,6 +66,7 @@
6166
#include "llvm/Support/Path.h"
6267
#include "llvm/Support/raw_ostream.h"
6368
#include <optional>
69+
#include <string>
6470
#include <vector>
6571

6672
namespace clang {
@@ -1310,6 +1316,63 @@ stringifyContainerForMainFileRef(const Decl *Container) {
13101316
return printQualifiedName(*ND);
13111317
return {};
13121318
}
1319+
1320+
std::optional<ReferencesResult>
1321+
maybeFindIncludeReferences(ParsedAST &AST, Position Pos,
1322+
URIForFile URIMainFile) {
1323+
const auto &Includes = AST.getIncludeStructure().MainFileIncludes;
1324+
auto IncludeOnLine = llvm::find_if(Includes, [&Pos](const Inclusion &Inc) {
1325+
return Inc.HashLine == Pos.line;
1326+
});
1327+
if (IncludeOnLine == Includes.end())
1328+
return std::nullopt;
1329+
1330+
const auto &Inc = *IncludeOnLine;
1331+
const SourceManager &SM = AST.getSourceManager();
1332+
ReferencesResult Results;
1333+
auto ConvertedMainFileIncludes = convertIncludes(SM, Includes);
1334+
auto ReferencedInclude = convertIncludes(SM, Inc);
1335+
include_cleaner::walkUsed(
1336+
AST.getLocalTopLevelDecls(), collectMacroReferences(AST),
1337+
AST.getPragmaIncludes(), SM,
1338+
[&](const include_cleaner::SymbolReference &Ref,
1339+
llvm::ArrayRef<include_cleaner::Header> Providers) {
1340+
if (Ref.RT != include_cleaner::RefType::Explicit)
1341+
return;
1342+
1343+
auto Provider =
1344+
firstMatchedProvider(ConvertedMainFileIncludes, Providers);
1345+
if (!Provider || ReferencedInclude.match(*Provider).empty())
1346+
return;
1347+
1348+
auto Loc = SM.getFileLoc(Ref.RefLocation);
1349+
// File locations can be outside of the main file if macro is
1350+
// expanded through an #include.
1351+
while (SM.getFileID(Loc) != SM.getMainFileID())
1352+
Loc = SM.getIncludeLoc(SM.getFileID(Loc));
1353+
1354+
ReferencesResult::Reference Result;
1355+
const auto *Token = AST.getTokens().spelledTokenAt(Loc);
1356+
Result.Loc.range = Range{sourceLocToPosition(SM, Token->location()),
1357+
sourceLocToPosition(SM, Token->endLocation())};
1358+
Result.Loc.uri = URIMainFile;
1359+
Results.References.push_back(std::move(Result));
1360+
});
1361+
if (Results.References.empty())
1362+
return std::nullopt;
1363+
1364+
// Add the #include line to the references list.
1365+
auto IncludeLen = std::string{"#include"}.length() + Inc.Written.length() + 1;
1366+
ReferencesResult::Reference Result;
1367+
Result.Loc.range = clangd::Range{Position{Inc.HashLine, 0},
1368+
Position{Inc.HashLine, (int)IncludeLen}};
1369+
Result.Loc.uri = URIMainFile;
1370+
Results.References.push_back(std::move(Result));
1371+
1372+
if (Results.References.empty())
1373+
return std::nullopt;
1374+
return Results;
1375+
}
13131376
} // namespace
13141377

13151378
ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
@@ -1324,6 +1387,11 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
13241387
return {};
13251388
}
13261389

1390+
const auto IncludeReferences =
1391+
maybeFindIncludeReferences(AST, Pos, URIMainFile);
1392+
if (IncludeReferences)
1393+
return *IncludeReferences;
1394+
13271395
llvm::DenseSet<SymbolID> IDsToQuery, OverriddenMethods;
13281396

13291397
const auto *IdentifierAtCursor =

Diff for: clang-tools-extra/clangd/unittests/HoverTests.cpp

+1-36
Original file line numberDiff line numberDiff line change
@@ -2999,36 +2999,7 @@ TEST(Hover, UsedSymbols) {
29992999
#in^clude <vector>
30003000
std::vector<int> vec;
30013001
)cpp",
3002-
[](HoverInfo &HI) { HI.UsedSymbolNames = {"vector"}; }},
3003-
{R"cpp(
3004-
#in^clude "public.h"
3005-
#include "private.h"
3006-
int fooVar = foo();
3007-
)cpp",
3008-
[](HoverInfo &HI) { HI.UsedSymbolNames = {"foo"}; }},
3009-
{R"cpp(
3010-
#include "bar.h"
3011-
#include "for^ward.h"
3012-
Bar *x;
3013-
)cpp",
3014-
[](HoverInfo &HI) {
3015-
HI.UsedSymbolNames = {
3016-
// No used symbols, since bar.h is a higher-ranked
3017-
// provider for Bar.
3018-
};
3019-
}},
3020-
{R"cpp(
3021-
#include "b^ar.h"
3022-
#define DEF(X) const Bar *X
3023-
DEF(a);
3024-
)cpp",
3025-
[](HoverInfo &HI) { HI.UsedSymbolNames = {"Bar"}; }},
3026-
{R"cpp(
3027-
#in^clude "bar.h"
3028-
#define BAZ(X) const X x
3029-
BAZ(Bar);
3030-
)cpp",
3031-
[](HoverInfo &HI) { HI.UsedSymbolNames = {"Bar"}; }}};
3002+
[](HoverInfo &HI) { HI.UsedSymbolNames = {"vector"}; }}};
30323003
for (const auto &Case : Cases) {
30333004
Annotations Code{Case.Code};
30343005
SCOPED_TRACE(Code.code());
@@ -3042,18 +3013,12 @@ TEST(Hover, UsedSymbols) {
30423013
int bar2();
30433014
class Bar {};
30443015
)cpp");
3045-
TU.AdditionalFiles["private.h"] = guard(R"cpp(
3046-
// IWYU pragma: private, include "public.h"
3047-
int foo();
3048-
)cpp");
3049-
TU.AdditionalFiles["public.h"] = guard("");
30503016
TU.AdditionalFiles["system/vector"] = guard(R"cpp(
30513017
namespace std {
30523018
template<typename>
30533019
class vector{};
30543020
}
30553021
)cpp");
3056-
TU.AdditionalFiles["forward.h"] = guard("class Bar;");
30573022
TU.ExtraArgs.push_back("-isystem" + testPath("system"));
30583023

30593024
auto AST = TU.build();

Diff for: clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "gmock/gmock.h"
3030
#include "gtest/gtest.h"
3131
#include <cstddef>
32+
#include <optional>
3233
#include <string>
3334
#include <utility>
3435
#include <vector>
@@ -435,6 +436,48 @@ TEST(IncludeCleaner, NoCrash) {
435436
MainCode.range());
436437
}
437438

439+
TEST(IncludeCleaner, FirstMatchedProvider) {
440+
struct {
441+
const char *Code;
442+
const std::vector<include_cleaner::Header> Providers;
443+
const std::optional<include_cleaner::Header> ExpectedProvider;
444+
} Cases[] = {
445+
{R"cpp(
446+
#include "bar.h"
447+
#include "foo.h"
448+
)cpp",
449+
{include_cleaner::Header{"bar.h"}, include_cleaner::Header{"foo.h"}},
450+
include_cleaner::Header{"bar.h"}},
451+
{R"cpp(
452+
#include "bar.h"
453+
#include "foo.h"
454+
)cpp",
455+
{include_cleaner::Header{"foo.h"}, include_cleaner::Header{"bar.h"}},
456+
include_cleaner::Header{"foo.h"}},
457+
{"#include \"bar.h\"",
458+
{include_cleaner::Header{"bar.h"}},
459+
include_cleaner::Header{"bar.h"}},
460+
{"#include \"bar.h\"", {include_cleaner::Header{"foo.h"}}, std::nullopt},
461+
{"#include \"bar.h\"", {}, std::nullopt}};
462+
for (const auto &Case : Cases) {
463+
Annotations Code{Case.Code};
464+
SCOPED_TRACE(Code.code());
465+
466+
TestTU TU;
467+
TU.Code = Code.code();
468+
TU.AdditionalFiles["bar.h"] = "";
469+
TU.AdditionalFiles["foo.h"] = "";
470+
471+
auto AST = TU.build();
472+
std::optional<include_cleaner::Header> MatchedProvider =
473+
firstMatchedProvider(
474+
convertIncludes(AST.getSourceManager(),
475+
AST.getIncludeStructure().MainFileIncludes),
476+
Case.Providers);
477+
EXPECT_EQ(MatchedProvider, Case.ExpectedProvider);
478+
}
479+
}
480+
438481
} // namespace
439482
} // namespace clangd
440483
} // namespace clang

Diff for: clang-tools-extra/clangd/unittests/XRefsTests.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ using ::testing::UnorderedElementsAre;
4343
using ::testing::UnorderedElementsAreArray;
4444
using ::testing::UnorderedPointwise;
4545

46+
std::string guard(llvm::StringRef Code) {
47+
return "#pragma once\n" + Code.str();
48+
}
49+
4650
MATCHER_P2(FileRange, File, Range, "") {
4751
return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg;
4852
}
@@ -2293,6 +2297,50 @@ TEST(FindReferences, ExplicitSymbols) {
22932297
checkFindRefs(Test);
22942298
}
22952299

2300+
TEST(FindReferences, UsedSymbolsFromInclude) {
2301+
const char *Tests[] = {
2302+
R"cpp([[#include ^"bar.h"]]
2303+
#include <vector>
2304+
int fstBar = [[bar1]]();
2305+
int sndBar = [[bar2]]();
2306+
[[Bar]] bar;
2307+
int macroBar = [[BAR]];
2308+
std::vector<int> vec;
2309+
)cpp",
2310+
2311+
R"cpp([[#in^clude <vector>]]
2312+
std::[[vector]]<int> vec;
2313+
)cpp"};
2314+
for (const char *Test : Tests) {
2315+
Annotations T(Test);
2316+
auto TU = TestTU::withCode(T.code());
2317+
TU.ExtraArgs.push_back("-std=c++20");
2318+
TU.AdditionalFiles["bar.h"] = guard(R"cpp(
2319+
#define BAR 5
2320+
int bar1();
2321+
int bar2();
2322+
class Bar {};
2323+
)cpp");
2324+
TU.AdditionalFiles["system/vector"] = guard(R"cpp(
2325+
namespace std {
2326+
template<typename>
2327+
class vector{};
2328+
}
2329+
)cpp");
2330+
TU.ExtraArgs.push_back("-isystem" + testPath("system"));
2331+
2332+
auto AST = TU.build();
2333+
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
2334+
for (const auto &R : T.ranges())
2335+
ExpectedLocations.push_back(AllOf(rangeIs(R), attrsAre(0u)));
2336+
for (const auto &P : T.points())
2337+
EXPECT_THAT(findReferences(AST, P, 0).References,
2338+
UnorderedElementsAreArray(ExpectedLocations))
2339+
<< "Failed for Refs at " << P << "\n"
2340+
<< Test;
2341+
}
2342+
}
2343+
22962344
TEST(FindReferences, NeedsIndexForSymbols) {
22972345
const char *Header = "int foo();";
22982346
Annotations Main("int main() { [[f^oo]](); }");

0 commit comments

Comments
 (0)