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 @@ -51,9 +51,11 @@ #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" +#include #include #include #include +#include #include namespace clang { @@ -266,7 +268,6 @@ } } // namespace - std::vector collectMacroReferences(ParsedAST &AST) { const auto &SM = AST.getSourceManager(); @@ -398,6 +399,10 @@ // FIXME: Use presumed locations to map such usages back to patched // locations safely. 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)); const auto *Token = AST.getTokens().spelledTokenAt(Loc); MissingIncludeDiagInfo DiagInfo{Ref.Target, Token->range(SM), Providers}; diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -384,6 +384,35 @@ EXPECT_THAT(Findings.UnusedIncludes, IsEmpty()); } +TEST(IncludeCleaner, MacroExpandedThroughIncludes) { + Annotations MainFile(R"cpp( + #include "all.h" + #define FOO(X) const Foo *X + void foo() { + #include [["expander.inc"]] + } +)cpp"); + + TestTU TU; + TU.AdditionalFiles["expander.inc"] = guard("FOO(f1);FOO(f2);"); + TU.AdditionalFiles["foo.h"] = guard("struct Foo {};"); + TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\""); + + TU.Code = MainFile.code(); + ParsedAST AST = TU.build(); + + auto Findings = computeIncludeCleanerFindings(AST).MissingIncludes; + // FIXME: Deduplicate references resulting from expansion of the same macro in + // multiple places. + EXPECT_THAT(Findings, testing::SizeIs(2)); + auto RefRange = Findings.front().SymRefRange; + auto &SM = AST.getSourceManager(); + EXPECT_EQ(RefRange.file(), SM.getMainFileID()); + // FIXME: Point at the spelling location, rather than the include. + EXPECT_EQ(halfOpenToRange(SM, RefRange.toCharRange(SM)), MainFile.range()); + EXPECT_EQ(RefRange, Findings[1].SymRefRange); +} + } // namespace } // namespace clangd } // namespace clang