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; @@ -179,6 +179,7 @@ if (GetClangTidyOptions) Opts.ClangTidyOpts = GetClangTidyOptions(*FS, File); Opts.SuggestMissingIncludes = SuggestMissingIncludes; + Opts.ForceRebuild = ForceRebuild; // Compile command is set asynchronously during update, as it can be slow. ParseInputs Inputs; 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 @@ -38,6 +38,7 @@ struct ParseOptions { tidy::ClangTidyOptions ClangTidyOpts; bool SuggestMissingIncludes = false; + bool ForceRebuild = false; }; /// Information required to run clang, e.g. to parse AST or do code completion. 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! + /// Disabled by default. This is useful to defeat clangd's assumption that + /// missing headers stay missing. + /// This is a clangd extension. + bool forceRebuild; }; 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 @@ -432,7 +432,8 @@ } std::shared_ptr OldPreamble = - getPossiblyStalePreamble(); + Inputs.Opts.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 @@ -615,6 +615,46 @@ 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"; + + // Return value indicates if the updated callback was received. + auto DoUpdate = [&](std::string Contents, bool ForceRebuild) -> bool { + std::atomic Updated(false); + Updated = false; + ParseInputs Inputs = getInputs(Source, std::string(Contents)); + Inputs.Opts.ForceRebuild = ForceRebuild; + updateWithDiags(S, Source, Inputs, WantDiagnostics::Yes, + [&Updated](std::vector) { Updated = true; }); + bool UpdateFinished = S.blockUntilIdle(timeoutSeconds(10)); + if (!UpdateFinished) + ADD_FAILURE() << "Updated has not finished in one second. Threading bug?"; + return Updated; + }; + + // Update the source contents, which should trigger an initial build with + // the header file missing. + ASSERT_TRUE(DoUpdate(SourceContents, /*ForceRebuild*/false)); + + // Add the header file. + Files[Header] = "int a;"; + Timestamps[Header] = time_t(1); + + // The addition of the missing header file shouldn't trigger a rebuild since + // we don't track missing files. + ASSERT_FALSE(DoUpdate(SourceContents, /*ForceRebuild*/false)); + + // Forcing the reload should should cause a rebuild, though. + ASSERT_TRUE(DoUpdate(SourceContents, /*ForceRebuild*/true)); +} TEST_F(TUSchedulerTests, NoChangeDiags) { TUScheduler S(CDB, optsForTest(), captureDiags()); @@ -721,7 +761,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(); }); @@ -745,7 +786,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(); });