diff --git a/clang-tools-extra/clangd/Compiler.h b/clang-tools-extra/clangd/Compiler.h --- a/clang-tools-extra/clangd/Compiler.h +++ b/clang-tools-extra/clangd/Compiler.h @@ -17,10 +17,12 @@ #include "../clang-tidy/ClangTidyOptions.h" #include "GlobalCompilationDatabase.h" +#include "Headers.h" #include "index/Index.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/PrecompiledPreamble.h" #include "clang/Tooling/CompilationDatabase.h" +#include namespace clang { namespace clangd { @@ -39,6 +41,7 @@ tidy::ClangTidyOptions ClangTidyOpts; bool SuggestMissingIncludes = false; bool BuildRecoveryAST = false; + std::vector AdditionalIncludes; }; /// Information required to run clang, e.g. to parse AST or do code completion. diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -262,6 +262,15 @@ std::string Filename = std::string(Buffer->getBufferIdentifier()); // Absolute. + // Process additional includes as implicit includes to ensure AST is built + // with necessary symbol information. + for (const auto &Inc : Opts.AdditionalIncludes) { + // Inc.Written contains quotes or angles, preprocessor requires them to be + // stripped. + CI->getPreprocessorOpts().Includes.emplace_back( + llvm::StringRef(Inc.Written).drop_back().drop_front()); + } + auto Clang = prepareCompilerInstance(std::move(CI), PreamblePCH, std::move(Buffer), VFS, ASTDiags); if (!Clang) @@ -431,6 +440,31 @@ std::vector D = ASTDiags.take(CTContext.getPointer()); Diags.insert(Diags.end(), D.begin(), D.end()); } + + // Add location information for additional includes. + auto &SM = Clang->getSourceManager(); + auto FID = SM.getMainFileID(); + const auto *Includer = SM.getFileEntryForID(FID); + auto FLoc = SM.getLocForStartOfFile(FID); + for (auto &Inc : Opts.AdditionalIncludes) { + Includes.MainFileIncludes.push_back(Inc); + + // Try to resolve it using header search. + const DirectoryLookup *CurDir = nullptr; + auto IncLoc = FLoc.getLocWithOffset(Inc.HashOffset); + auto FER = Clang->getPreprocessor().getHeaderSearchInfo().LookupFile( + llvm::StringRef(Inc.Written).drop_front().drop_back(), IncLoc, + Inc.Written.front() == '<', nullptr, CurDir, + {{Includer, Includer->getDir()}}, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr); + if (!FER) + continue; + auto &FE = FER->getFileEntry(); + Includes.MainFileIncludes.back().Resolved = FE.tryGetRealPathName().str(); + Includes.recordInclude(Includer->getName(), FE.getName(), + FE.tryGetRealPathName()); + } + return ParsedAST(Version, std::move(Preamble), std::move(Clang), std::move(Action), std::move(Tokens), std::move(Macros), std::move(ParsedDecls), std::move(Diags), diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp @@ -17,7 +17,9 @@ #include "Annotations.h" #include "Compiler.h" #include "Diagnostics.h" +#include "Headers.h" #include "ParsedAST.h" +#include "Preamble.h" #include "SourceCode.h" #include "TestFS.h" #include "TestTU.h" @@ -28,6 +30,7 @@ #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Token.h" #include "clang/Tooling/Syntax/Tokens.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ScopedPrinter.h" #include "gmock/gmock-matchers.h" @@ -82,6 +85,13 @@ return arg.beginOffset() == R.Begin && arg.endOffset() == R.End; } +MATCHER(EqInc, "") { + Inclusion Actual = testing::get<0>(arg); + Inclusion Expected = testing::get<1>(arg); + return std::tie(Actual.HashOffset, Actual.R, Actual.Written) == + std::tie(Expected.HashOffset, Expected.R, Expected.Written); +} + TEST(ParsedASTTest, TopLevelDecls) { TestTU TU; TU.HeaderCode = R"( @@ -420,6 +430,73 @@ } } +TEST(ParsedASTTest, AdditionalIncludes) { + TestTU TU; + TU.Filename = "foo.cpp"; + TU.AdditionalFiles["foo.h"] = "void foo();"; + TU.AdditionalFiles["sub/baz.h"] = "void baz();"; + TU.AdditionalFiles["sub/aux.h"] = "void aux();"; + TU.ExtraArgs = {"-I" + testPath("sub")}; + TU.Code = R"cpp( + #include "baz.h" + #include "foo.h" + #include "sub/aux.h" + void bar() { + foo(); + baz(); + aux(); + })cpp"; + auto ExpectedAST = TU.build(); + + // Build preamble with no includes. + TU.Code = R"cpp( + void bar() { + foo(); + baz(); + aux(); + })cpp"; + StoreDiags Diags; + auto Inputs = TU.inputs(); + auto CI = buildCompilerInvocation(Inputs, Diags); + auto Preamble = buildPreamble("foo.cpp", *CI, Inputs, true, nullptr); + ASSERT_TRUE(Preamble); + EXPECT_THAT(Preamble->Includes.MainFileIncludes, testing::IsEmpty()); + + // Now build an AST using additional includes and check that locations are + // correctly parsed. + TU.Code = R"cpp( + #include "baz.h" + #include "foo.h" + #include "sub/aux.h" + void bar() { + foo(); + baz(); + aux(); + })cpp"; + Inputs = TU.inputs(); + Inputs.Opts.AdditionalIncludes = + getPreambleIncludes(TU.Code, ExpectedAST.getLangOpts()); + auto PatchedAST = buildAST("foo.cpp", std::move(CI), {}, Inputs, Preamble); + ASSERT_TRUE(PatchedAST); + + // Ensure source location information is correct. + EXPECT_THAT(PatchedAST->getIncludeStructure().MainFileIncludes, + testing::Pointwise( + EqInc(), ExpectedAST.getIncludeStructure().MainFileIncludes)); + auto StringMapToVector = [](const llvm::StringMap SM) { + std::vector> Res; + for (const auto &E : SM) + Res.push_back({E.first().str(), E.second}); + llvm::sort(Res); + return Res; + }; + // Ensure file proximity signals are correct. + EXPECT_EQ(StringMapToVector(PatchedAST->getIncludeStructure().includeDepth( + testPath("foo.cpp"))), + StringMapToVector(ExpectedAST.getIncludeStructure().includeDepth( + testPath("foo.cpp")))); +} + } // namespace } // namespace clangd } // namespace clang