diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -21,6 +21,7 @@ #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringSet.h" +#include "llvm/Support/JSON.h" #include namespace clang { @@ -57,10 +58,11 @@ private: // Implement ClangdServer::Callbacks. - void onDiagnosticsReady(PathRef File, std::vector Diagnostics) override; + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, + std::vector Diagnostics) override; void onFileUpdated(PathRef File, const TUStatus &Status) override; void - onHighlightingsReady(PathRef File, + onHighlightingsReady(PathRef File, llvm::StringRef Version, std::vector Highlightings) override; void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override; 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 @@ -41,6 +41,21 @@ namespace clang { namespace clangd { namespace { + +// LSP defines file versions as numbers that increase. +// ClangdServer treats them as opaque and therefore uses strings instead. +std::string encodeVersion(int64_t LSPVersion) { + return llvm::to_string(LSPVersion); +} +llvm::Optional decodeVersion(llvm::StringRef Encoded) { + int64_t Result; + if (llvm::to_integer(Encoded, Result, 10)) + return Result; + else if (!Encoded.empty()) // Empty can be e.g. diagnostics on close. + elog("unexpected non-numeric version {0}", Encoded); + return llvm::None; +} + /// Transforms a tweak into a code action that would apply it if executed. /// EXPECTS: T.prepare() was called and returned true. CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File, @@ -630,8 +645,9 @@ const std::string &Contents = Params.textDocument.text; - DraftMgr.addDraft(File, Params.textDocument.version, Contents); - Server->addDocument(File, Contents, WantDiagnostics::Yes); + auto Version = DraftMgr.addDraft(File, Params.textDocument.version, Contents); + Server->addDocument(File, Contents, encodeVersion(Version), + WantDiagnostics::Yes); } void ClangdLSPServer::onDocumentDidChange( @@ -654,7 +670,8 @@ return; } - Server->addDocument(File, Draft->Contents, WantDiags, Params.forceRebuild); + Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version), + WantDiags, Params.forceRebuild); } void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { @@ -1347,7 +1364,8 @@ } void ClangdLSPServer::onHighlightingsReady( - PathRef File, std::vector Highlightings) { + PathRef File, llvm::StringRef Version, + std::vector Highlightings) { std::vector Old; std::vector HighlightingsCopy = Highlightings; { @@ -1358,14 +1376,18 @@ // LSP allows us to send incremental edits of highlightings. Also need to diff // to remove highlightings from tokens that should no longer have them. std::vector Diffed = diffHighlightings(Highlightings, Old); - publishSemanticHighlighting( - {{URIForFile::canonicalize(File, /*TUPath=*/File)}, - toSemanticHighlightingInformation(Diffed)}); + SemanticHighlightingParams Notification; + Notification.TextDocument.uri = + URIForFile::canonicalize(File, /*TUPath=*/File); + Notification.TextDocument.version = decodeVersion(Version); + Notification.Lines = toSemanticHighlightingInformation(Diffed); + publishSemanticHighlighting(Notification); } -void ClangdLSPServer::onDiagnosticsReady(PathRef File, +void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) { PublishDiagnosticsParams Notification; + Notification.version = decodeVersion(Version); Notification.uri = URIForFile::canonicalize(File, /*TUPath=*/File); DiagnosticToReplacementMap LocalFixIts; // Temporary storage for (auto &Diag : Diagnostics) { @@ -1475,8 +1497,10 @@ // Reparse only opened files that were modified. for (const Path &FilePath : DraftMgr.getActiveFiles()) if (ModifiedFiles.find(FilePath) != ModifiedFiles.end()) - Server->addDocument(FilePath, DraftMgr.getDraft(FilePath)->Contents, - WantDiagnostics::Auto); + if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? + Server->addDocument(FilePath, std::move(Draft->Contents), + encodeVersion(Draft->Version), + WantDiagnostics::Auto); } } // namespace clangd 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 @@ -69,7 +69,7 @@ /// Called by ClangdServer when \p Diagnostics for \p File are ready. /// May be called concurrently for separate files, not for a single file. - virtual void onDiagnosticsReady(PathRef File, + virtual void onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) {} /// Called whenever the file status is updated. /// May be called concurrently for separate files, not for a single file. @@ -78,7 +78,7 @@ /// Called by ClangdServer when some \p Highlightings for \p File are ready. /// May be called concurrently for separate files, not for a single file. virtual void - onHighlightingsReady(PathRef File, + onHighlightingsReady(PathRef File, llvm::StringRef Version, std::vector Highlightings) {} /// Called when background indexing tasks are enqueued/started/completed. @@ -171,13 +171,17 @@ /// \p File is already tracked. Also schedules parsing of the AST for it on a /// separate thread. When the parsing is complete, DiagConsumer passed in /// constructor will receive onDiagnosticsReady callback. + /// Version identifies this snapshot and is propagated to ASTs, preambles, + /// diagnostics etc built from it. void addDocument(PathRef File, StringRef Contents, + llvm::StringRef Version = "null", WantDiagnostics WD = WantDiagnostics::Auto, bool ForceRebuild = false); /// Remove \p File from list of tracked files, schedule a request to free /// resources associated with it. Pending diagnostics for closed files may not /// be delivered, even if requested with WantDiags::Auto or WantDiags::Yes. + /// An empty set of diagnostics will be delivered, with Version = "". void removeDocument(PathRef File); /// Run code completion for \p File at \p Pos. 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 @@ -62,11 +62,11 @@ : FIndex(FIndex), ServerCallbacks(ServerCallbacks), SemanticHighlighting(SemanticHighlighting) {} - void onPreambleAST(PathRef Path, ASTContext &Ctx, + void onPreambleAST(PathRef Path, llvm::StringRef Version, ASTContext &Ctx, std::shared_ptr PP, const CanonicalIncludes &CanonIncludes) override { if (FIndex) - FIndex->updatePreamble(Path, Ctx, std::move(PP), CanonIncludes); + FIndex->updatePreamble(Path, Version, Ctx, std::move(PP), CanonIncludes); } void onMainAST(PathRef Path, ParsedAST &AST, PublishFn Publish) override { @@ -80,16 +80,19 @@ if (ServerCallbacks) Publish([&]() { - ServerCallbacks->onDiagnosticsReady(Path, std::move(Diagnostics)); + ServerCallbacks->onDiagnosticsReady(Path, AST.version(), + std::move(Diagnostics)); if (SemanticHighlighting) - ServerCallbacks->onHighlightingsReady(Path, std::move(Highlightings)); + ServerCallbacks->onHighlightingsReady(Path, AST.version(), + std::move(Highlightings)); }); } - void onFailedAST(PathRef Path, std::vector Diags, - PublishFn Publish) override { + void onFailedAST(PathRef Path, llvm::StringRef Version, + std::vector Diags, PublishFn Publish) override { if (ServerCallbacks) - Publish([&]() { ServerCallbacks->onDiagnosticsReady(Path, Diags); }); + Publish( + [&]() { ServerCallbacks->onDiagnosticsReady(Path, Version, Diags); }); } void onFileUpdated(PathRef File, const TUStatus &Status) override { @@ -169,6 +172,7 @@ } void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents, + llvm::StringRef Version, WantDiagnostics WantDiags, bool ForceRebuild) { auto FS = FSProvider.getFileSystem(); @@ -183,6 +187,7 @@ ParseInputs Inputs; Inputs.FS = FS; Inputs.Contents = std::string(Contents); + Inputs.Version = Version.str(); Inputs.ForceRebuild = ForceRebuild; Inputs.Opts = std::move(Opts); Inputs.Index = Index; 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,8 @@ tooling::CompileCommand CompileCommand; IntrusiveRefCntPtr FS; std::string Contents; + // Version identifier for Contents, provided by the client and opaque to us. + std::string Version = "null"; // Prevent reuse of the cached preamble/AST. Slow! Useful to workaround // clangd's assumption that missing header files will stay missing. bool ForceRebuild = false; diff --git a/clang-tools-extra/clangd/ParsedAST.h b/clang-tools-extra/clangd/ParsedAST.h --- a/clang-tools-extra/clangd/ParsedAST.h +++ b/clang-tools-extra/clangd/ParsedAST.h @@ -48,7 +48,7 @@ /// Attempts to run Clang and store parsed AST. If \p Preamble is non-null /// it is reused during parsing. static llvm::Optional - build(std::unique_ptr CI, + build(llvm::StringRef Version, std::unique_ptr CI, llvm::ArrayRef CompilerInvocationDiags, std::shared_ptr Preamble, std::unique_ptr Buffer, @@ -101,14 +101,19 @@ /// (!) does not have tokens from the preamble. const syntax::TokenBuffer &getTokens() const { return Tokens; } + /// Returns the version of the ParseInputs this AST was built from. + llvm::StringRef version() const { return Version; } + private: - ParsedAST(std::shared_ptr Preamble, + ParsedAST(llvm::StringRef Version, + std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector LocalTopLevelDecls, std::vector Diags, IncludeStructure Includes, CanonicalIncludes CanonIncludes); + std::string Version; // In-memory preambles must outlive the AST, it is important that this member // goes before Clang and Action. std::shared_ptr Preamble; 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 @@ -239,7 +239,8 @@ } llvm::Optional -ParsedAST::build(std::unique_ptr CI, +ParsedAST::build(llvm::StringRef Version, + std::unique_ptr CI, llvm::ArrayRef CompilerInvocationDiags, std::shared_ptr Preamble, std::unique_ptr Buffer, @@ -427,10 +428,10 @@ std::vector D = ASTDiags.take(CTContext.getPointer()); Diags.insert(Diags.end(), D.begin(), D.end()); } - return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), - std::move(Tokens), std::move(Macros), std::move(ParsedDecls), - std::move(Diags), std::move(Includes), - std::move(CanonIncludes)); + return ParsedAST(Version, std::move(Preamble), std::move(Clang), + std::move(Action), std::move(Tokens), std::move(Macros), + std::move(ParsedDecls), std::move(Diags), + std::move(Includes), std::move(CanonIncludes)); } ParsedAST::ParsedAST(ParsedAST &&Other) = default; @@ -512,14 +513,15 @@ return CanonIncludes; } -ParsedAST::ParsedAST(std::shared_ptr Preamble, +ParsedAST::ParsedAST(llvm::StringRef Version, + std::shared_ptr Preamble, std::unique_ptr Clang, std::unique_ptr Action, syntax::TokenBuffer Tokens, MainFileMacros Macros, std::vector LocalTopLevelDecls, std::vector Diags, IncludeStructure Includes, CanonicalIncludes CanonIncludes) - : Preamble(std::move(Preamble)), Clang(std::move(Clang)), + : Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)), Action(std::move(Action)), Tokens(std::move(Tokens)), Macros(std::move(Macros)), Diags(std::move(Diags)), LocalTopLevelDecls(std::move(LocalTopLevelDecls)), @@ -546,7 +548,7 @@ } return ParsedAST::build( - std::make_unique(*Invocation), + Inputs.Version, std::make_unique(*Invocation), CompilerInvocationDiags, Preamble, llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName), std::move(VFS), Inputs.Index, Inputs.Opts); diff --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-extra/clangd/Preamble.h --- a/clang-tools-extra/clangd/Preamble.h +++ b/clang-tools-extra/clangd/Preamble.h @@ -43,11 +43,14 @@ /// As we must avoid re-parsing the preamble, any information that can only /// be obtained during parsing must be eagerly captured and stored here. struct PreambleData { - PreambleData(PrecompiledPreamble Preamble, std::vector Diags, - IncludeStructure Includes, MainFileMacros Macros, + PreambleData(llvm::StringRef Version, PrecompiledPreamble Preamble, + std::vector Diags, IncludeStructure Includes, + MainFileMacros Macros, std::unique_ptr StatCache, CanonicalIncludes CanonIncludes); + // Version of the ParseInputs this preamble was built from. + std::string Version; tooling::CompileCommand CompileCommand; PrecompiledPreamble Preamble; std::vector Diags; diff --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-tools-extra/clangd/Preamble.cpp --- a/clang-tools-extra/clangd/Preamble.cpp +++ b/clang-tools-extra/clangd/Preamble.cpp @@ -75,12 +75,13 @@ } // namespace -PreambleData::PreambleData(PrecompiledPreamble Preamble, +PreambleData::PreambleData(llvm::StringRef Version, + PrecompiledPreamble Preamble, std::vector Diags, IncludeStructure Includes, MainFileMacros Macros, std::unique_ptr StatCache, CanonicalIncludes CanonIncludes) - : Preamble(std::move(Preamble)), Diags(std::move(Diags)), + : Version(Version), Preamble(std::move(Preamble)), Diags(std::move(Diags)), Includes(std::move(Includes)), Macros(std::move(Macros)), StatCache(std::move(StatCache)), CanonIncludes(std::move(CanonIncludes)) { } @@ -102,12 +103,17 @@ compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) && OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds, Inputs.FS.get())) { - vlog("Reusing preamble for {0}", FileName); + vlog("Reusing preamble version {0} for version {1} of {2}", + OldPreamble->Version, Inputs.Version, FileName); return OldPreamble; } - vlog(OldPreamble ? "Rebuilding invalidated preamble for {0}" - : "Building first preamble for {0}", - FileName); + if (OldPreamble) + vlog("Rebuilding invalidated preamble for {0} version {1} " + "(previous was version {2})", + FileName, Inputs.Version, OldPreamble->Version); + else + vlog("Building first preamble for {0} verson {1}", FileName, + Inputs.Version); trace::Span Tracer("BuildPreamble"); SPAN_ATTACH(Tracer, "File", FileName); @@ -145,16 +151,17 @@ CI.getFrontendOpts().SkipFunctionBodies = false; if (BuiltPreamble) { - vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(), - FileName); + vlog("Built preamble of size {0} for file {1} version {2}", + BuiltPreamble->getSize(), FileName, Inputs.Version); std::vector Diags = PreambleDiagnostics.take(); return std::make_shared( - std::move(*BuiltPreamble), std::move(Diags), + Inputs.Version, std::move(*BuiltPreamble), std::move(Diags), SerializedDeclsCollector.takeIncludes(), SerializedDeclsCollector.takeMacros(), std::move(StatCache), SerializedDeclsCollector.takeCanonicalIncludes()); } else { - elog("Could not build a preamble for file {0}", FileName); + elog("Could not build a preamble for file {0} version {2}", FileName, + Inputs.Version); return nullptr; } } 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 @@ -125,14 +125,16 @@ bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &); struct VersionedTextDocumentIdentifier : public TextDocumentIdentifier { - // The version number of this document. If a versioned text document - // identifier is sent from the server to the client and the file is not open - // in the editor (the server has not received an open notification before) the - // server can send `null` to indicate that the version is known and the - // content on disk is the master (as speced with document content ownership). - // - // The version number of a document will increase after each change, including - // undo/redo. The number doesn't need to be consecutive. + /// The version number of this document. If a versioned text document + /// identifier is sent from the server to the client and the file is not open + /// in the editor (the server has not received an open notification before) + /// the server can send `null` to indicate that the version is known and the + /// content on disk is the master (as speced with document content ownership). + /// + /// The version number of a document will increase after each change, + /// including undo/redo. The number doesn't need to be consecutive. + /// + /// clangd extension: versions are optional, and synthesized if missing. llvm::Optional version; }; llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &); @@ -237,7 +239,10 @@ std::string languageId; /// The version number of this document (it will strictly increase after each - std::int64_t version = 0; + /// change, including undo/redo. + /// + /// clangd extension: versions are optional, and synthesized if missing. + llvm::Optional version; /// The content of the opened text document. std::string text; @@ -811,6 +816,8 @@ URIForFile uri; /// An array of diagnostic information items. std::vector diagnostics; + /// The version number of the document the diagnostics are published for. + llvm::Optional version; }; llvm::json::Value toJSON(const PublishDiagnosticsParams &); @@ -1353,7 +1360,7 @@ /// Parameters for the semantic highlighting (server-side) push notification. struct SemanticHighlightingParams { /// The textdocument these highlightings belong to. - TextDocumentIdentifier TextDocument; + VersionedTextDocumentIdentifier TextDocument; /// The lines of highlightings that should be sent. std::vector Lines; }; 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 @@ -92,8 +92,7 @@ llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) { auto Result = toJSON(static_cast(R)); - if (R.version) - Result.getAsObject()->try_emplace("version", R.version); + Result.getAsObject()->try_emplace("version", R.version); return Result; } @@ -547,8 +546,9 @@ llvm::json::Value toJSON(const PublishDiagnosticsParams &PDP) { return llvm::json::Object{ - {"uri", PDP.uri}, - {"diagnostics", PDP.diagnostics}, + {"uri", PDP.uri}, + {"diagnostics", PDP.diagnostics}, + {"version", PDP.version}, }; } diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h --- a/clang-tools-extra/clangd/TUScheduler.h +++ b/clang-tools-extra/clangd/TUScheduler.h @@ -122,7 +122,8 @@ /// Called on the AST that was built for emitting the preamble. The built AST /// contains only AST nodes from the #include directives at the start of the /// file. AST node in the current file should be observed on onMainAST call. - virtual void onPreambleAST(PathRef Path, ASTContext &Ctx, + virtual void onPreambleAST(PathRef Path, llvm::StringRef Version, + ASTContext &Ctx, std::shared_ptr PP, const CanonicalIncludes &) {} @@ -153,8 +154,8 @@ /// Called whenever the AST fails to build. \p Diags will have the diagnostics /// that led to failure. - virtual void onFailedAST(PathRef Path, std::vector Diags, - PublishFn Publish) {} + virtual void onFailedAST(PathRef Path, llvm::StringRef Version, + std::vector Diags, PublishFn Publish) {} /// Called whenever the TU status is updated. virtual void onFileUpdated(PathRef File, const TUStatus &Status) {} 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 @@ -375,7 +375,7 @@ } void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) { - llvm::StringRef TaskName = "Update"; + std::string TaskName = llvm::formatv("Update ({0})", Inputs.Version); auto Task = [=]() mutable { auto RunPublish = [&](llvm::function_ref Publish) { // Ensure we only publish results from the worker if the file was not @@ -409,8 +409,8 @@ } RanASTCallback = false; emitTUStatus({TUAction::BuildingPreamble, TaskName}); - log("Updating file {0} with command {1}\n[{2}]\n{3}", FileName, - Inputs.CompileCommand.Heuristic, + log("ASTWorker building file {0} version {1} with command {2}\n[{3}]\n{4}", + FileName, Inputs.Version, Inputs.CompileCommand.Heuristic, Inputs.CompileCommand.Directory, llvm::join(Inputs.CompileCommand.CommandLine, " ")); // Rebuild the preamble and the AST. @@ -431,8 +431,8 @@ Details.BuildFailed = true; emitTUStatus({TUAction::BuildingPreamble, TaskName}, &Details); // Report the diagnostics we collected when parsing the command line. - Callbacks.onFailedAST(FileName, std::move(CompilerInvocationDiags), - RunPublish); + Callbacks.onFailedAST(FileName, Inputs.Version, + std::move(CompilerInvocationDiags), RunPublish); // Make sure anyone waiting for the preamble gets notified it could not // be built. PreambleWasBuilt.notify(); @@ -445,9 +445,11 @@ std::shared_ptr NewPreamble = buildPreamble( FileName, *Invocation, OldPreamble, OldCommand, Inputs, StorePreambleInMemory, - [this](ASTContext &Ctx, std::shared_ptr PP, - const CanonicalIncludes &CanonIncludes) { - Callbacks.onPreambleAST(FileName, Ctx, std::move(PP), CanonIncludes); + [this, Version(Inputs.Version)]( + ASTContext &Ctx, std::shared_ptr PP, + const CanonicalIncludes &CanonIncludes) { + Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP), + CanonIncludes); }); bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble); @@ -541,7 +543,8 @@ // line if there were any. // FIXME: we might have got more errors while trying to build the AST, // surface them too. - Callbacks.onFailedAST(FileName, CompilerInvocationDiags, RunPublish); + Callbacks.onFailedAST(FileName, Inputs.Version, CompilerInvocationDiags, + RunPublish); } // Stash the AST in the cache for further use. IdleASTs.put(this, std::move(*AST)); @@ -563,6 +566,8 @@ std::unique_ptr Invocation = buildCompilerInvocation( *CurrentInputs, CompilerInvocationDiagConsumer); // Try rebuilding the AST. + vlog("ASTWorker rebuilding evicted AST to run {0}: {1} version {2}", Name, + FileName, CurrentInputs->Version); llvm::Optional NewAST = Invocation ? buildAST(FileName, @@ -579,6 +584,8 @@ if (!*AST) return Action(llvm::make_error( "invalid AST", llvm::errc::invalid_argument)); + vlog("ASTWorker running {0} on version {2} of {1}", Name, FileName, + CurrentInputs->Version); Action(InputsAndAST{*CurrentInputs, **AST}); }; startTask(Name, std::move(Task), /*UpdateType=*/None, Invalidation); @@ -788,8 +795,10 @@ I->UpdateType = WantDiagnostics::Auto; } - while (shouldSkipHeadLocked()) + while (shouldSkipHeadLocked()) { + vlog("ASTWorker skipping {0} for {1}", Requests.front().Name, FileName); Requests.pop_front(); + } assert(!Requests.empty() && "skipped the whole queue"); // Some updates aren't dead yet, but never end up being used. // e.g. the first keystroke is live until obsoleted by the second. diff --git a/clang-tools-extra/clangd/index/FileIndex.h b/clang-tools-extra/clangd/index/FileIndex.h --- a/clang-tools-extra/clangd/index/FileIndex.h +++ b/clang-tools-extra/clangd/index/FileIndex.h @@ -98,7 +98,7 @@ /// Update preamble symbols of file \p Path with all declarations in \p AST /// and macros in \p PP. - void updatePreamble(PathRef Path, ASTContext &AST, + void updatePreamble(PathRef Path, llvm::StringRef Version, ASTContext &AST, std::shared_ptr PP, const CanonicalIncludes &Includes); @@ -142,7 +142,8 @@ /// Index declarations from \p AST and macros from \p PP that are declared in /// included headers. -SlabTuple indexHeaderSymbols(ASTContext &AST, std::shared_ptr PP, +SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST, + std::shared_ptr PP, const CanonicalIncludes &Includes); } // namespace clangd diff --git a/clang-tools-extra/clangd/index/FileIndex.cpp b/clang-tools-extra/clangd/index/FileIndex.cpp --- a/clang-tools-extra/clangd/index/FileIndex.cpp +++ b/clang-tools-extra/clangd/index/FileIndex.cpp @@ -35,7 +35,7 @@ llvm::ArrayRef DeclsToIndex, const MainFileMacros *MacroRefsToIndex, const CanonicalIncludes &Includes, - bool IsIndexMainAST) { + bool IsIndexMainAST, llvm::StringRef Version) { SymbolCollector::Options CollectorOpts; CollectorOpts.CollectIncludePath = true; CollectorOpts.Includes = &Includes; @@ -74,12 +74,13 @@ auto Refs = Collector.takeRefs(); auto Relations = Collector.takeRelations(); - vlog("index AST for {0} (main={1}): \n" - " symbol slab: {2} symbols, {3} bytes\n" - " ref slab: {4} symbols, {5} refs, {6} bytes\n" - " relations slab: {7} relations, {8} bytes", - FileName, IsIndexMainAST, Syms.size(), Syms.bytes(), Refs.size(), - Refs.numRefs(), Refs.bytes(), Relations.size(), Relations.bytes()); + vlog("indexed {0} AST for {1} version {2}:\n" + " symbol slab: {3} symbols, {4} bytes\n" + " ref slab: {5} symbols, {6} refs, {7} bytes\n" + " relations slab: {8} relations, {9} bytes", + IsIndexMainAST ? "file" : "preamble", FileName, Version, Syms.size(), + Syms.bytes(), Refs.size(), Refs.numRefs(), Refs.bytes(), + Relations.size(), Relations.bytes()); return std::make_tuple(std::move(Syms), std::move(Refs), std::move(Relations)); } @@ -88,17 +89,18 @@ return indexSymbols(AST.getASTContext(), AST.getPreprocessorPtr(), AST.getLocalTopLevelDecls(), &AST.getMacros(), AST.getCanonicalIncludes(), - /*IsIndexMainAST=*/true); + /*IsIndexMainAST=*/true, AST.version()); } -SlabTuple indexHeaderSymbols(ASTContext &AST, std::shared_ptr PP, +SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST, + std::shared_ptr PP, const CanonicalIncludes &Includes) { std::vector DeclsToIndex( AST.getTranslationUnitDecl()->decls().begin(), AST.getTranslationUnitDecl()->decls().end()); return indexSymbols(AST, std::move(PP), DeclsToIndex, /*MainFileMacros=*/nullptr, Includes, - /*IsIndexMainAST=*/false); + /*IsIndexMainAST=*/false, Version); } void FileSymbols::update(PathRef Path, std::unique_ptr Symbols, @@ -248,10 +250,11 @@ PreambleIndex(std::make_unique()), MainFileIndex(std::make_unique()) {} -void FileIndex::updatePreamble(PathRef Path, ASTContext &AST, +void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version, + ASTContext &AST, std::shared_ptr PP, const CanonicalIncludes &Includes) { - auto Slabs = indexHeaderSymbols(AST, std::move(PP), Includes); + auto Slabs = indexHeaderSymbols(Version, AST, std::move(PP), Includes); PreambleSymbols.update( Path, std::make_unique(std::move(std::get<0>(Slabs))), std::make_unique(), diff --git a/clang-tools-extra/clangd/test/diagnostic-category.test b/clang-tools-extra/clangd/test/diagnostic-category.test --- a/clang-tools-extra/clangd/test/diagnostic-category.test +++ b/clang-tools-extra/clangd/test/diagnostic-category.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"categorySupport":true}}},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"struct Point {}; union Point p;"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -37,7 +37,8 @@ # CHECK-NEXT: "severity": 3 # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":4,"method":"shutdown"} diff --git a/clang-tools-extra/clangd/test/diagnostics-no-tidy.test b/clang-tools-extra/clangd/test/diagnostics-no-tidy.test --- a/clang-tools-extra/clangd/test/diagnostics-no-tidy.test +++ b/clang-tools-extra/clangd/test/diagnostics-no-tidy.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test -clang-tidy=false < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {\n(void)sizeof(42);\n}"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"void main() {\n(void)sizeof(42);\n}"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -22,7 +22,8 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":2,"method":"sync","params":null} @@ -31,7 +32,8 @@ # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": null # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":5,"method":"shutdown"} diff --git a/clang-tools-extra/clangd/test/diagnostics-notes.test b/clang-tools-extra/clangd/test/diagnostics-notes.test --- a/clang-tools-extra/clangd/test/diagnostics-notes.test +++ b/clang-tools-extra/clangd/test/diagnostics-notes.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"relatedInformation":true}}},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cc","languageId":"cpp","version":1,"text":"int x;\nint x;"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cc","languageId":"cpp","text":"int x;\nint x;"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -40,7 +40,8 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.cc" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.cc", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":5,"method":"shutdown"} diff --git a/clang-tools-extra/clangd/test/diagnostics.test b/clang-tools-extra/clangd/test/diagnostics.test --- a/clang-tools-extra/clangd/test/diagnostics.test +++ b/clang-tools-extra/clangd/test/diagnostics.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test -clang-tidy-checks=bugprone-sizeof-expression < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {\n(void)sizeof(42);\n}"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"void main() {\n(void)sizeof(42);\n}"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -38,7 +38,8 @@ # CHECK-NEXT: "source": "clang-tidy" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":2,"method":"sync","params":null} @@ -47,7 +48,8 @@ # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": null # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":5,"method":"shutdown"} diff --git a/clang-tools-extra/clangd/test/did-change-configuration-params.test b/clang-tools-extra/clangd/test/did-change-configuration-params.test --- a/clang-tools-extra/clangd/test/did-change-configuration-params.test +++ b/clang-tools-extra/clangd/test/did-change-configuration-params.test @@ -5,18 +5,20 @@ --- {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test", "compilationCommand": ["clang", "-c", "foo.c"]}}}}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main() { int i; return i; }"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","text":"int main() { int i; return i; }"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/bar.c" +# CHECK-NEXT: "uri": "file://{{.*}}/bar.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test2", "compilationCommand": ["clang", "-c", "foo.c", "-Wall", "-Werror"]}}}}} @@ -40,10 +42,11 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } # -# ERR: Updating file {{.*}}foo.c with command +# ERR: ASTWorker building file {{.*}}foo.c version 0 with command # ERR: [{{.*}}clangd-test2] # ERR: clang -c foo.c -Wall -Werror --- diff --git a/clang-tools-extra/clangd/test/execute-command.test b/clang-tools-extra/clangd/test/execute-command.test --- a/clang-tools-extra/clangd/test/execute-command.test +++ b/clang-tools-extra/clangd/test/execute-command.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -22,7 +22,8 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- # No command name diff --git a/clang-tools-extra/clangd/test/fixits-codeaction.test b/clang-tools-extra/clangd/test/fixits-codeaction.test --- a/clang-tools-extra/clangd/test/fixits-codeaction.test +++ b/clang-tools-extra/clangd/test/fixits-codeaction.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"codeAction":{"codeActionLiteralSupport":{}}}},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -22,7 +22,8 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)", "code": "-Wparentheses", "source": "clang"}]}}} diff --git a/clang-tools-extra/clangd/test/fixits-command.test b/clang-tools-extra/clangd/test/fixits-command.test --- a/clang-tools-extra/clangd/test/fixits-command.test +++ b/clang-tools-extra/clangd/test/fixits-command.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"int main(int i, char **a) { if (i = 2) {}}"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -22,7 +22,8 @@ # CHECK-NEXT: "source": "clang" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)"}]}}} diff --git a/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test b/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test --- a/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test +++ b/clang-tools-extra/clangd/test/fixits-embed-in-diagnostic.test @@ -1,7 +1,7 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"codeActionsInline":true}}},"trace":"off"}} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":"struct Point {}; union Point p;"}}} # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [ @@ -61,7 +61,8 @@ # CHECK-NEXT: "severity": 3 # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- {"jsonrpc":"2.0","id":4,"method":"shutdown"} diff --git a/clang-tools-extra/clangd/test/path-mappings.test b/clang-tools-extra/clangd/test/path-mappings.test --- a/clang-tools-extra/clangd/test/path-mappings.test +++ b/clang-tools-extra/clangd/test/path-mappings.test @@ -12,7 +12,6 @@ "textDocument": { "uri": "file:///C:/client/bar.cpp", "languageId": "cpp", - "version": 1, "text": "#include \"foo.h\"\nint main(){\nreturn foo();\n}" } } @@ -21,7 +20,8 @@ # CHECK: "method": "textDocument/publishDiagnostics", # CHECK-NEXT: "params": { # CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file:///C:/client/bar.cpp" +# CHECK-NEXT: "uri": "file:///C:/client/bar.cpp", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } --- # We're editing bar.cpp, which includes foo.h, where foo.h "exists" at a server location @@ -47,7 +47,7 @@ # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { # CHECK-NEXT: "character": {{[0-9]+}}, -# CHECK-NEXT: "line": {{[0-9]+}} +# CHECK-NEXT: "line": {{[0-9]+}} # CHECK-NEXT: }, # CHECK-NEXT: "start": { # CHECK-NEXT: "character": {{[0-9]+}}, diff --git a/clang-tools-extra/clangd/test/semantic-highlighting.test b/clang-tools-extra/clangd/test/semantic-highlighting.test --- a/clang-tools-extra/clangd/test/semantic-highlighting.test +++ b/clang-tools-extra/clangd/test/semantic-highlighting.test @@ -67,7 +67,7 @@ # CHECK-NEXT: ] # CHECK-NEXT: }, --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","version":1,"text":"int x = 2;"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","text":"int x = 2;"}}} # CHECK: "method": "textDocument/semanticHighlighting", # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ @@ -78,12 +78,13 @@ # CHECK-NEXT: } # CHECK-NEXT: ], # CHECK-NEXT: "textDocument": { -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp" +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT:} --- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo2.cpp","languageId":"cpp","version":1,"text":"int x = 2;\nint y = 2;"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo2.cpp","languageId":"cpp","text":"int x = 2;\nint y = 2;"}}} # CHECK: "method": "textDocument/semanticHighlighting", # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ @@ -99,12 +100,13 @@ # CHECK-NEXT: } # CHECK-NEXT: ], # CHECK-NEXT: "textDocument": { -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo2.cpp" +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo2.cpp", +# CHECK-NEXT: "version": 0 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT:} --- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp","version":2},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 0,"character": 10}},"rangeLength": 0,"text": "\nint y = 2;"}]}} +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp"},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 0,"character": 10}},"rangeLength": 0,"text": "\nint y = 2;"}]}} # CHECK: "method": "textDocument/semanticHighlighting", # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ @@ -115,12 +117,13 @@ # CHECK-NEXT: } # CHECK-NEXT: ], # CHECK-NEXT: "textDocument": { -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp" +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp", +# CHECK-NEXT: "version": 1 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT:} --- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp","version":2},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 1,"character": 10}},"rangeLength": 11,"text": ""}]}} +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.cpp"},"contentChanges": [{"range":{"start": {"line": 0,"character": 10},"end": {"line": 1,"character": 10}},"rangeLength": 11,"text": ""}]}} # CHECK: "method": "textDocument/semanticHighlighting", # CHECK-NEXT: "params": { # CHECK-NEXT: "lines": [ @@ -131,7 +134,8 @@ # CHECK-NEXT: } # CHECK-NEXT: ], # CHECK-NEXT: "textDocument": { -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp" +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/foo.cpp", +# CHECK-NEXT: "version": 2 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT:} diff --git a/clang-tools-extra/clangd/test/version.test b/clang-tools-extra/clangd/test/version.test new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/test/version.test @@ -0,0 +1,25 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# Verify versions get recorded/inferred, and are reported in publishDiagnostics. +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","text":""}}} +# CHECK: "version": 0 +--- +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":5},"contentChanges":[{"text":"a"}]}} +# CHECK: "version": 5 +--- +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c"},"contentChanges":[{"text":"b"}]}} +# CHECK: "version": 6 +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","version": 42, "languageId":"c","text":""}}} +# CHECK: "version": 42 +--- +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///bar.c"},"contentChanges":[{"text":"c"}]}} +# CHECK: "version": 43 +--- +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///bar.c", "version": 123},"contentChanges":[{"text":"d"}]}} +# CHECK: "version": 123 +--- +{"jsonrpc":"2.0","id":6,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -61,7 +61,7 @@ class ErrorCheckingCallbacks : public ClangdServer::Callbacks { public: - void onDiagnosticsReady(PathRef File, + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) override { bool HadError = diagsContainErrors(Diagnostics); std::lock_guard Lock(Mutex); @@ -82,7 +82,7 @@ /// least one error. class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks { public: - void onDiagnosticsReady(PathRef File, + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) override { bool HadError = diagsContainErrors(Diagnostics); @@ -276,7 +276,7 @@ mutable int Got; } FS; struct Callbacks : public ClangdServer::Callbacks { - void onDiagnosticsReady(PathRef File, + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) override { Got = Context::current().getExisting(Secret); } @@ -295,6 +295,23 @@ EXPECT_EQ(Callbacks.Got, 42); } +TEST_F(ClangdVFSTest, PropagatesVersion) { + MockCompilationDatabase CDB; + MockFSProvider FS; + struct Callbacks : public ClangdServer::Callbacks { + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, + std::vector Diagnostics) override { + Got = Version.str(); + } + std::string Got = ""; + } Callbacks; + + // Verify that the version is plumbed to diagnostics. + ClangdServer Server(CDB, FS, ClangdServer::optsForTest(), &Callbacks); + runAddDocument(Server, testPath("foo.cpp"), "void main(){}", "42"); + EXPECT_EQ(Callbacks.Got, "42"); +} + // Only enable this test on Unix #ifdef LLVM_ON_UNIX TEST_F(ClangdVFSTest, SearchLibDir) { @@ -374,7 +391,7 @@ // Now switch to C++ mode. CDB.ExtraClangFlags = {"-xc++"}; - runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto); + runAddDocument(Server, FooCpp, SourceContents2); EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); // Subsequent addDocument calls should finish without errors too. runAddDocument(Server, FooCpp, SourceContents1); @@ -406,7 +423,7 @@ // Parse without the define, no errors should be produced. CDB.ExtraClangFlags = {}; - runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto); + runAddDocument(Server, FooCpp, SourceContents); ASSERT_TRUE(Server.blockUntilIdleForTest()); EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); // Subsequent addDocument call should finish without errors too. @@ -467,8 +484,8 @@ CDB.ExtraClangFlags.clear(); DiagConsumer.clear(); Server.removeDocument(BazCpp); - Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto); - Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto); + Server.addDocument(FooCpp, FooSource.code()); + Server.addDocument(BarCpp, BarSource.code()); ASSERT_TRUE(Server.blockUntilIdleForTest()); EXPECT_THAT(DiagConsumer.filesWithDiags(), @@ -595,7 +612,7 @@ public: TestDiagConsumer() : Stats(FilesCount, FileStat()) {} - void onDiagnosticsReady(PathRef File, + void onDiagnosticsReady(PathRef File, llvm::StringRef Version, std::vector Diagnostics) override { StringRef FileIndexStr = llvm::sys::path::stem(File); ASSERT_TRUE(FileIndexStr.consume_front("Foo")); @@ -672,8 +689,7 @@ bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen); Server.addDocument(FilePaths[FileIndex], ShouldHaveErrors ? SourceContentsWithErrors - : SourceContentsWithoutErrors, - WantDiagnostics::Auto); + : SourceContentsWithoutErrors); UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors); }; @@ -775,7 +791,8 @@ NoConcurrentAccessDiagConsumer(std::promise StartSecondReparse) : StartSecondReparse(std::move(StartSecondReparse)) {} - void onDiagnosticsReady(PathRef, std::vector) override { + void onDiagnosticsReady(PathRef, llvm::StringRef, + std::vector) override { ++Count; std::unique_lock Lock(Mutex, std::try_to_lock_t()); ASSERT_TRUE(Lock.owns_lock()) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -1509,7 +1509,7 @@ } int a = fun^ )cpp"); - Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes); + Server.addDocument(FooCpp, Source.code(), "null", WantDiagnostics::Yes); // We need to wait for preamble to build. ASSERT_TRUE(Server.blockUntilIdleForTest()); @@ -1575,7 +1575,7 @@ // FIXME: Auto-completion in a template requires disabling delayed template // parsing. CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing"); - runAddDocument(Server, FooCpp, Source.code(), WantDiagnostics::Yes); + runAddDocument(Server, FooCpp, Source.code(), "null", WantDiagnostics::Yes); CodeCompleteResult Completions = cantFail(runCodeComplete( Server, FooCpp, Source.point(), clangd::CodeCompleteOptions())); diff --git a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp --- a/clang-tools-extra/clangd/unittests/FileIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/FileIndexTests.cpp @@ -151,8 +151,8 @@ File.HeaderFilename = (Basename + ".h").str(); File.HeaderCode = std::string(Code); auto AST = File.build(); - M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); + M.updatePreamble(File.Filename, /*Version=*/"null", AST.getASTContext(), + AST.getPreprocessorPtr(), AST.getCanonicalIncludes()); } TEST(FileIndexTest, CustomizedURIScheme) { @@ -293,7 +293,8 @@ const CanonicalIncludes &CanonIncludes) { EXPECT_FALSE(IndexUpdated) << "Expected only a single index update"; IndexUpdated = true; - Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes); + Index.updatePreamble(FooCpp, /*Version=*/"null", Ctx, std::move(PP), + CanonIncludes); }); ASSERT_TRUE(IndexUpdated); @@ -392,7 +393,7 @@ TU.HeaderCode = "class A {}; class B : public A {};"; auto AST = TU.build(); FileIndex Index; - Index.updatePreamble(TU.Filename, AST.getASTContext(), + Index.updatePreamble(TU.Filename, /*Version=*/"null", AST.getASTContext(), AST.getPreprocessorPtr(), AST.getCanonicalIncludes()); SymbolID A = findSymbol(TU.headerSymbols(), "A").ID; uint32_t Results = 0; diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp @@ -702,7 +702,8 @@ std::atomic Count = {0}; void onHighlightingsReady( - PathRef File, std::vector Highlightings) override { + PathRef File, llvm::StringRef Version, + std::vector Highlightings) override { ++Count; } }; diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.h b/clang-tools-extra/clangd/unittests/SyncAPI.h --- a/clang-tools-extra/clangd/unittests/SyncAPI.h +++ b/clang-tools-extra/clangd/unittests/SyncAPI.h @@ -23,7 +23,9 @@ // Calls addDocument and then blockUntilIdleForTest. void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents, - WantDiagnostics WantDiags = WantDiagnostics::Auto); + StringRef Version = "null", + WantDiagnostics WantDiags = WantDiagnostics::Auto, + bool ForceRebuild = false); llvm::Expected runCodeComplete(ClangdServer &Server, PathRef File, Position Pos, diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.cpp b/clang-tools-extra/clangd/unittests/SyncAPI.cpp --- a/clang-tools-extra/clangd/unittests/SyncAPI.cpp +++ b/clang-tools-extra/clangd/unittests/SyncAPI.cpp @@ -13,8 +13,9 @@ namespace clangd { void runAddDocument(ClangdServer &Server, PathRef File, - llvm::StringRef Contents, WantDiagnostics WantDiags) { - Server.addDocument(File, Contents, WantDiags); + llvm::StringRef Contents, llvm::StringRef Version, + WantDiagnostics WantDiags, bool ForceRebuild) { + Server.addDocument(File, Contents, Version, WantDiags, ForceRebuild); if (!Server.blockUntilIdleForTest()) llvm_unreachable("not idle after addDocument"); } 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 @@ -41,7 +41,15 @@ using ::testing::UnorderedElementsAre; MATCHER_P2(TUState, State, ActionName, "") { - return arg.Action.S == State && arg.Action.Name == ActionName; + if (arg.Action.S != State) { + *result_listener << "state is " << arg.Action.S; + return false; + } + if (arg.Action.Name != ActionName) { + *result_listener << "name is " << arg.Action.Name; + return false; + } + return true; } TUScheduler::Options optsForTest() { @@ -62,8 +70,15 @@ void updateWithCallback(TUScheduler &S, PathRef File, llvm::StringRef Contents, WantDiagnostics WD, llvm::unique_function CB) { + updateWithCallback(S, File, getInputs(File, std::string(Contents)), WD, + std::move(CB)); + } + + void updateWithCallback(TUScheduler &S, PathRef File, ParseInputs Inputs, + WantDiagnostics WD, + llvm::unique_function CB) { WithContextValue Ctx(llvm::make_scope_exit(std::move(CB))); - S.update(File, getInputs(File, std::string(Contents)), WD); + S.update(File, Inputs, WD); } static Key)>> @@ -78,8 +93,8 @@ reportDiagnostics(File, AST.getDiagnostics(), Publish); } - void onFailedAST(PathRef File, std::vector Diags, - PublishFn Publish) override { + void onFailedAST(PathRef File, llvm::StringRef Version, + std::vector Diags, PublishFn Publish) override { reportDiagnostics(File, Diags, Publish); } @@ -244,7 +259,9 @@ // Schedule two updates (A, B) and two preamble reads (stale, consistent). // The stale read should see A, and the consistent read should see B. // (We recognize the preambles by their included files). - updateWithCallback(S, Path, "#include ", WantDiagnostics::Yes, [&]() { + auto Inputs = getInputs(Path, "#include "); + Inputs.Version = "A"; + updateWithCallback(S, Path, Inputs, WantDiagnostics::Yes, [&]() { // This callback runs in between the two preamble updates. // This blocks update B, preventing it from winning the race @@ -257,12 +274,14 @@ // If the second read was stale, it would usually see A. std::this_thread::sleep_for(std::chrono::milliseconds(100)); }); - S.update(Path, getInputs(Path, "#include "), WantDiagnostics::Yes); + Inputs.Contents = "#include "; + Inputs.Version = "B"; + S.update(Path, Inputs, WantDiagnostics::Yes); S.runWithPreamble("StaleRead", Path, TUScheduler::Stale, [&](Expected Pre) { ASSERT_TRUE(bool(Pre)); - assert(bool(Pre)); + EXPECT_EQ(Pre->Preamble->Version, "A"); EXPECT_THAT(includes(Pre->Preamble), ElementsAre("")); InconsistentReadDone.notify(); @@ -271,6 +290,7 @@ S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent, [&](Expected Pre) { ASSERT_TRUE(bool(Pre)); + EXPECT_EQ(Pre->Preamble->Version, "B"); EXPECT_THAT(includes(Pre->Preamble), ElementsAre("")); ++CallbackCount; @@ -446,6 +466,7 @@ auto Inputs = getInputs(File, Contents.str()); { WithContextValue WithNonce(NonceKey, ++Nonce); + Inputs.Version = Nonce; updateWithDiags( S, File, Inputs, WantDiagnostics::Auto, [File, Nonce, &Mut, &TotalUpdates](std::vector) { @@ -467,6 +488,8 @@ ASSERT_TRUE((bool)AST); EXPECT_EQ(AST->Inputs.FS, Inputs.FS); EXPECT_EQ(AST->Inputs.Contents, Inputs.Contents); + EXPECT_EQ(AST->Inputs.Version, Inputs.Version); + EXPECT_EQ(AST->AST.version(), Inputs.Version); std::lock_guard Lock(Mut); ++TotalASTReads; @@ -769,9 +792,6 @@ TEST_F(TUSchedulerTests, TUStatus) { class CaptureTUStatus : public ClangdServer::Callbacks { public: - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override {} - void onFileUpdated(PathRef File, const TUStatus &Status) override { std::lock_guard Lock(Mutex); AllStatus.push_back(Status); @@ -793,7 +813,8 @@ // We schedule the following tasks in the queue: // [Update] [GoToDefinition] - Server.addDocument(testPath("foo.cpp"), Code.code(), WantDiagnostics::Yes); + Server.addDocument(testPath("foo.cpp"), Code.code(), "1", + WantDiagnostics::Yes); Server.locateSymbolAt(testPath("foo.cpp"), Code.point(), [](Expected> Result) { ASSERT_TRUE((bool)Result); @@ -804,9 +825,9 @@ EXPECT_THAT(CaptureTUStatus.allStatus(), ElementsAre( // Statuses of "Update" action. - TUState(TUAction::RunningAction, "Update"), - TUState(TUAction::BuildingPreamble, "Update"), - TUState(TUAction::BuildingFile, "Update"), + TUState(TUAction::RunningAction, "Update (1)"), + TUState(TUAction::BuildingPreamble, "Update (1)"), + TUState(TUAction::BuildingFile, "Update (1)"), // Statuses of "Definitions" action TUState(TUAction::RunningAction, "Definitions"), diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/clang-tools-extra/clangd/unittests/TestTU.cpp @@ -97,7 +97,7 @@ SymbolSlab TestTU::headerSymbols() const { auto AST = build(); - return std::get<0>(indexHeaderSymbols(AST.getASTContext(), + return std::get<0>(indexHeaderSymbols(/*Version=*/"null", AST.getASTContext(), AST.getPreprocessorPtr(), AST.getCanonicalIncludes())); } @@ -105,8 +105,8 @@ std::unique_ptr TestTU::index() const { auto AST = build(); auto Idx = std::make_unique(/*UseDex=*/true); - Idx->updatePreamble(Filename, AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); + Idx->updatePreamble(Filename, /*Version=*/"null", AST.getASTContext(), + AST.getPreprocessorPtr(), AST.getCanonicalIncludes()); Idx->updateMain(Filename, AST); return std::move(Idx); } diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -849,7 +849,8 @@ ElementsAre(Sym("foo.h", FooHeader.range()))); // Only preamble is built, and no AST is built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::No); + Server.addDocument(FooCpp, FooWithoutHeader.code(), "null", + WantDiagnostics::No); // We build AST here, and it should use the latest preamble rather than the // stale one. EXPECT_THAT( @@ -859,7 +860,8 @@ // Reset test environment. runAddDocument(Server, FooCpp, FooWithHeader.code()); // Both preamble and AST are built in this request. - Server.addDocument(FooCpp, FooWithoutHeader.code(), WantDiagnostics::Yes); + Server.addDocument(FooCpp, FooWithoutHeader.code(), "null", + WantDiagnostics::Yes); // Use the AST being built in above request. EXPECT_THAT( cantFail(runLocateSymbolAt(Server, FooCpp, FooWithoutHeader.point())),