diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -647,7 +647,7 @@ return; } - Server->addDocument(File, *Contents, WantDiags); + Server->addDocument(File, *Contents, WantDiags, Params.forceRebuild); } void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -172,7 +172,8 @@ /// separate thread. When the parsing is complete, DiagConsumer passed in /// constructor will receive onDiagnosticsReady callback. void addDocument(PathRef File, StringRef Contents, - WantDiagnostics WD = WantDiagnostics::Auto); + WantDiagnostics WD = WantDiagnostics::Auto, + bool ForceRebuild = false); /// Get the contents of \p File, which should have been added. llvm::StringRef getDocument(PathRef File) const; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -170,7 +170,7 @@ } void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, - WantDiagnostics WantDiags) { + WantDiagnostics WantDiags, bool ForceRebuild) { auto FS = FSProvider.getFileSystem(); ParseOptions Opts; @@ -184,6 +184,7 @@ ParseInputs Inputs; Inputs.FS = FS; Inputs.Contents = std::string(Contents); + Inputs.ForceRebuild = ForceRebuild; Inputs.Opts = std::move(Opts); Inputs.Index = Index; bool NewFile = WorkScheduler.update(File, Inputs, WantDiags); 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 @@ -45,6 +45,9 @@ tooling::CompileCommand CompileCommand; IntrusiveRefCntPtr FS; std::string Contents; + // Prevent reuse of the cached preamble/AST. Slow! Useful to workaround + // clangd's assumption that missing header files will stay missing. + bool ForceRebuild = false; // Used to recover from diagnostics (e.g. find missing includes for symbol). const SymbolIndex *Index = nullptr; ParseOptions Opts; diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -652,6 +652,12 @@ /// either they will be provided for this version or some subsequent one. /// This is a clangd extension. llvm::Optional wantDiagnostics; + + /// Force a complete rebuild of the file, ignoring all cached state. Slow! + /// This is useful to defeat clangd's assumption that missing headers will + /// stay missing. + /// This is a clangd extension. + bool forceRebuild = false; }; bool fromJSON(const llvm::json::Value &, DidChangeTextDocumentParams &); diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -430,7 +430,10 @@ bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R) { llvm::json::ObjectMapper O(Params); - return O && O.map("textDocument", R.textDocument) && + if (!O) + return false; + O.map("forceRebuild", R.forceRebuild); // Optional clangd extension. + return O.map("textDocument", R.textDocument) && O.map("contentChanges", R.contentChanges) && O.map("wantDiagnostics", R.wantDiagnostics); } diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp --- a/clang-tools-extra/clangd/TUScheduler.cpp +++ b/clang-tools-extra/clangd/TUScheduler.cpp @@ -436,7 +436,8 @@ } std::shared_ptr OldPreamble = - getPossiblyStalePreamble(); + Inputs.ForceRebuild ? std::shared_ptr() + : getPossiblyStalePreamble(); std::shared_ptr NewPreamble = buildPreamble( FileName, *Invocation, OldPreamble, OldCommand, Inputs, StorePreambleInMemory, diff --git a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp --- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp +++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp @@ -616,6 +616,53 @@ ASSERT_FALSE(DoUpdate(OtherSourceContents)); } +TEST_F(TUSchedulerTests, ForceRebuild) { + TUScheduler S(CDB, optsForTest(), captureDiags()); + + auto Source = testPath("foo.cpp"); + auto Header = testPath("foo.h"); + + auto SourceContents = R"cpp( + #include "foo.h" + int b = a; + )cpp"; + + ParseInputs Inputs = getInputs(Source, SourceContents); + + // Update the source contents, which should trigger an initial build with + // the header file missing. + updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes, + [](std::vector Diags) { + EXPECT_THAT( + Diags, + ElementsAre( + Field(&Diag::Message, "'foo.h' file not found"), + Field(&Diag::Message, "use of undeclared identifier 'a'"))); + }); + + // Add the header file. We need to recreate the inputs since we changed a + // file from underneath the test FS. + Files[Header] = "int a;"; + Timestamps[Header] = time_t(1); + Inputs = getInputs(Source, SourceContents); + + // The addition of the missing header file shouldn't trigger a rebuild since + // we don't track missing files. + updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes, + [](std::vector Diags) { + ADD_FAILURE() << "Did not expect diagnostics for missing header update"; + }); + + // Forcing the reload should should cause a rebuild which no longer has any + // errors. + Inputs.ForceRebuild = true; + updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes, + [](std::vector Diags) { + EXPECT_THAT(Diags, IsEmpty()); + }); + + ASSERT_TRUE(S.blockUntilIdle(timeoutSeconds(10))); +} TEST_F(TUSchedulerTests, NoChangeDiags) { TUScheduler S(CDB, optsForTest(), captureDiags()); @@ -722,7 +769,8 @@ TUScheduler S(CDB, optsForTest(), captureDiags()); std::vector Diagnostics; updateWithDiags(S, testPath("foo.cpp"), "void test() {}", - WantDiagnostics::Yes, [&](std::vector D) { + WantDiagnostics::Yes, + [&](std::vector D) { Diagnostics = std::move(D); Ready.notify(); }); @@ -746,7 +794,8 @@ TUScheduler S(CDB, optsForTest(), captureDiags()); std::vector Diagnostics; updateWithDiags(S, testPath("foo.cpp"), "void test() {}", - WantDiagnostics::Yes, [&](std::vector D) { + WantDiagnostics::Yes, + [&](std::vector D) { Diagnostics = std::move(D); Ready.notify(); });