[clangd] Track document versions, include them with diags, enhance logs

Summary:
This ties to an LSP feature (diagnostic versioning) but really a lot
of the value is in being able to log what's happening with file versions
and queues more descriptively and clearly.

As such it's fairly invasive, for a logging patch :-\

Key decisions:
 - at the LSP layer, we don't reqire the client to provide versions (LSP
   makes it mandatory but we never enforced it). If not provided,
   versions start at 0 and increment. DraftStore handles this.
 - don't propagate magically using contexts, but rather manually:
   addDocument -> ParseInputs -> (ParsedAST, Preamble, various callbacks)
   Context-propagation would hide the versions from ClangdServer, which
   would make producing good log messages hard
 - within ClangdServer, treat versions as opaque and unordered.
   std::string is a convenient type for this, and allows richer versions
   for embedders. They're "mandatory" but "null" is a reasonable default.

Subscribers: ilya-biryukov, javed.absar, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D75582
This commit is contained in:
Sam McCall
2020-03-04 00:33:29 +01:00
parent e6d9b2cb92
commit 2cd33e6fe6
36 changed files with 321 additions and 159 deletions

View File

@@ -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<int64_t> 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 @@ void ClangdLSPServer::onDocumentDidOpen(
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 @@ void ClangdLSPServer::onDocumentDidChange(
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 @@ bool ClangdLSPServer::shouldRunCompletion(
}
void ClangdLSPServer::onHighlightingsReady(
PathRef File, std::vector<HighlightingToken> Highlightings) {
PathRef File, llvm::StringRef Version,
std::vector<HighlightingToken> Highlightings) {
std::vector<HighlightingToken> Old;
std::vector<HighlightingToken> HighlightingsCopy = Highlightings;
{
@@ -1358,14 +1376,18 @@ void ClangdLSPServer::onHighlightingsReady(
// 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<LineHighlightings> 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<Diag> 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 @@ void ClangdLSPServer::reparseOpenedFiles(
// 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

View File

@@ -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 <memory>
namespace clang {
@@ -57,10 +58,11 @@ public:
private:
// Implement ClangdServer::Callbacks.
void onDiagnosticsReady(PathRef File, std::vector<Diag> Diagnostics) override;
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diagnostics) override;
void onFileUpdated(PathRef File, const TUStatus &Status) override;
void
onHighlightingsReady(PathRef File,
onHighlightingsReady(PathRef File, llvm::StringRef Version,
std::vector<HighlightingToken> Highlightings) override;
void onBackgroundIndexProgress(const BackgroundQueue::Stats &Stats) override;

View File

@@ -62,11 +62,11 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
: FIndex(FIndex), ServerCallbacks(ServerCallbacks),
SemanticHighlighting(SemanticHighlighting) {}
void onPreambleAST(PathRef Path, ASTContext &Ctx,
void onPreambleAST(PathRef Path, llvm::StringRef Version, ASTContext &Ctx,
std::shared_ptr<clang::Preprocessor> 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 @@ struct UpdateIndexCallbacks : public ParsingCallbacks {
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<Diag> Diags,
PublishFn Publish) override {
void onFailedAST(PathRef Path, llvm::StringRef Version,
std::vector<Diag> 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 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
}
void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
llvm::StringRef Version,
WantDiagnostics WantDiags, bool ForceRebuild) {
auto FS = FSProvider.getFileSystem();
@@ -183,6 +187,7 @@ void ClangdServer::addDocument(PathRef File, llvm::StringRef Contents,
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;

View File

@@ -69,7 +69,7 @@ public:
/// 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<Diag> Diagnostics) {}
/// Called whenever the file status is updated.
/// May be called concurrently for separate files, not for a single file.
@@ -78,7 +78,7 @@ public:
/// 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<HighlightingToken> Highlightings) {}
/// Called when background indexing tasks are enqueued/started/completed.
@@ -171,13 +171,17 @@ public:
/// \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.

View File

@@ -45,6 +45,8 @@ struct ParseInputs {
tooling::CompileCommand CompileCommand;
IntrusiveRefCntPtr<llvm::vfs::FileSystem> 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;

View File

@@ -239,7 +239,8 @@ void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
}
llvm::Optional<ParsedAST>
ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
ParsedAST::build(llvm::StringRef Version,
std::unique_ptr<clang::CompilerInvocation> CI,
llvm::ArrayRef<Diag> CompilerInvocationDiags,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
@@ -427,10 +428,10 @@ ParsedAST::build(std::unique_ptr<clang::CompilerInvocation> CI,
std::vector<Diag> 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 @@ const CanonicalIncludes &ParsedAST::getCanonicalIncludes() const {
return CanonIncludes;
}
ParsedAST::ParsedAST(std::shared_ptr<const PreambleData> Preamble,
ParsedAST::ParsedAST(llvm::StringRef Version,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action,
syntax::TokenBuffer Tokens, MainFileMacros Macros,
std::vector<Decl *> LocalTopLevelDecls,
std::vector<Diag> 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 @@ buildAST(PathRef FileName, std::unique_ptr<CompilerInvocation> Invocation,
}
return ParsedAST::build(
std::make_unique<CompilerInvocation>(*Invocation),
Inputs.Version, std::make_unique<CompilerInvocation>(*Invocation),
CompilerInvocationDiags, Preamble,
llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, FileName),
std::move(VFS), Inputs.Index, Inputs.Opts);

View File

@@ -48,7 +48,7 @@ public:
/// Attempts to run Clang and store parsed AST. If \p Preamble is non-null
/// it is reused during parsing.
static llvm::Optional<ParsedAST>
build(std::unique_ptr<clang::CompilerInvocation> CI,
build(llvm::StringRef Version, std::unique_ptr<clang::CompilerInvocation> CI,
llvm::ArrayRef<Diag> CompilerInvocationDiags,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<llvm::MemoryBuffer> Buffer,
@@ -101,14 +101,19 @@ public:
/// (!) 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<const PreambleData> Preamble,
ParsedAST(llvm::StringRef Version,
std::shared_ptr<const PreambleData> Preamble,
std::unique_ptr<CompilerInstance> Clang,
std::unique_ptr<FrontendAction> Action, syntax::TokenBuffer Tokens,
MainFileMacros Macros, std::vector<Decl *> LocalTopLevelDecls,
std::vector<Diag> 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<const PreambleData> Preamble;

View File

@@ -75,12 +75,13 @@ private:
} // namespace
PreambleData::PreambleData(PrecompiledPreamble Preamble,
PreambleData::PreambleData(llvm::StringRef Version,
PrecompiledPreamble Preamble,
std::vector<Diag> Diags, IncludeStructure Includes,
MainFileMacros Macros,
std::unique_ptr<PreambleFileStatusCache> 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 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
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 @@ buildPreamble(PathRef FileName, CompilerInvocation &CI,
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<Diag> Diags = PreambleDiagnostics.take();
return std::make_shared<PreambleData>(
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;
}
}

View File

@@ -43,11 +43,14 @@ namespace clangd {
/// 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<Diag> Diags,
IncludeStructure Includes, MainFileMacros Macros,
PreambleData(llvm::StringRef Version, PrecompiledPreamble Preamble,
std::vector<Diag> Diags, IncludeStructure Includes,
MainFileMacros Macros,
std::unique_ptr<PreambleFileStatusCache> StatCache,
CanonicalIncludes CanonIncludes);
// Version of the ParseInputs this preamble was built from.
std::string Version;
tooling::CompileCommand CompileCommand;
PrecompiledPreamble Preamble;
std::vector<Diag> Diags;

View File

@@ -92,8 +92,7 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R) {
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) {
auto Result = toJSON(static_cast<const TextDocumentIdentifier &>(R));
if (R.version)
Result.getAsObject()->try_emplace("version", R.version);
Result.getAsObject()->try_emplace("version", R.version);
return Result;
}
@@ -547,8 +546,9 @@ bool fromJSON(const llvm::json::Value &Params, Diagnostic &R) {
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},
};
}

View File

@@ -125,14 +125,16 @@ llvm::json::Value toJSON(const TextDocumentIdentifier &);
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<std::int64_t> version;
};
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &);
@@ -237,7 +239,10 @@ struct TextDocumentItem {
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<int64_t> version;
/// The content of the opened text document.
std::string text;
@@ -811,6 +816,8 @@ struct PublishDiagnosticsParams {
URIForFile uri;
/// An array of diagnostic information items.
std::vector<Diagnostic> diagnostics;
/// The version number of the document the diagnostics are published for.
llvm::Optional<int64_t> version;
};
llvm::json::Value toJSON(const PublishDiagnosticsParams &);
@@ -1353,7 +1360,7 @@ llvm::json::Value toJSON(const SemanticHighlightingInformation &Highlighting);
/// 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<SemanticHighlightingInformation> Lines;
};

View File

@@ -375,7 +375,7 @@ ASTWorker::~ASTWorker() {
}
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<void()> Publish) {
// Ensure we only publish results from the worker if the file was not
@@ -409,8 +409,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
}
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 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
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 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
std::shared_ptr<const PreambleData> NewPreamble = buildPreamble(
FileName, *Invocation, OldPreamble, OldCommand, Inputs,
StorePreambleInMemory,
[this](ASTContext &Ctx, std::shared_ptr<clang::Preprocessor> PP,
const CanonicalIncludes &CanonIncludes) {
Callbacks.onPreambleAST(FileName, Ctx, std::move(PP), CanonIncludes);
[this, Version(Inputs.Version)](
ASTContext &Ctx, std::shared_ptr<clang::Preprocessor> PP,
const CanonicalIncludes &CanonIncludes) {
Callbacks.onPreambleAST(FileName, Version, Ctx, std::move(PP),
CanonIncludes);
});
bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble);
@@ -541,7 +543,8 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags) {
// 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 @@ void ASTWorker::runWithAST(
std::unique_ptr<CompilerInvocation> 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<ParsedAST> NewAST =
Invocation
? buildAST(FileName,
@@ -579,6 +584,8 @@ void ASTWorker::runWithAST(
if (!*AST)
return Action(llvm::make_error<llvm::StringError>(
"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 @@ Deadline ASTWorker::scheduleLocked() {
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.

View File

@@ -122,7 +122,8 @@ public:
/// 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<clang::Preprocessor> PP,
const CanonicalIncludes &) {}
@@ -153,8 +154,8 @@ public:
/// Called whenever the AST fails to build. \p Diags will have the diagnostics
/// that led to failure.
virtual void onFailedAST(PathRef Path, std::vector<Diag> Diags,
PublishFn Publish) {}
virtual void onFailedAST(PathRef Path, llvm::StringRef Version,
std::vector<Diag> Diags, PublishFn Publish) {}
/// Called whenever the TU status is updated.
virtual void onFileUpdated(PathRef File, const TUStatus &Status) {}

View File

@@ -35,7 +35,7 @@ static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
llvm::ArrayRef<Decl *> 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 @@ static SlabTuple indexSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
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 @@ SlabTuple indexMainDecls(ParsedAST &AST) {
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<Preprocessor> PP,
SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
std::shared_ptr<Preprocessor> PP,
const CanonicalIncludes &Includes) {
std::vector<Decl *> 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<SymbolSlab> Symbols,
@@ -248,10 +250,11 @@ FileIndex::FileIndex(bool UseDex)
PreambleIndex(std::make_unique<MemIndex>()),
MainFileIndex(std::make_unique<MemIndex>()) {}
void FileIndex::updatePreamble(PathRef Path, ASTContext &AST,
void FileIndex::updatePreamble(PathRef Path, llvm::StringRef Version,
ASTContext &AST,
std::shared_ptr<Preprocessor> 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<SymbolSlab>(std::move(std::get<0>(Slabs))),
std::make_unique<RefSlab>(),

View File

@@ -98,7 +98,7 @@ public:
/// 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<Preprocessor> PP,
const CanonicalIncludes &Includes);
@@ -142,7 +142,8 @@ SlabTuple indexMainDecls(ParsedAST &AST);
/// Index declarations from \p AST and macros from \p PP that are declared in
/// included headers.
SlabTuple indexHeaderSymbols(ASTContext &AST, std::shared_ptr<Preprocessor> PP,
SlabTuple indexHeaderSymbols(llvm::StringRef Version, ASTContext &AST,
std::shared_ptr<Preprocessor> PP,
const CanonicalIncludes &Includes);
} // namespace clangd

View File

@@ -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"}

View File

@@ -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"}

View File

@@ -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"}

View File

@@ -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"}

View File

@@ -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
---

View File

@@ -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

View File

@@ -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"}]}}}

View File

@@ -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)"}]}}}

View File

@@ -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"}

View File

@@ -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]+}},

View File

@@ -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:}

View File

@@ -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"}

View File

@@ -61,7 +61,7 @@ bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
class ErrorCheckingCallbacks : public ClangdServer::Callbacks {
public:
void onDiagnosticsReady(PathRef File,
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
@@ -82,7 +82,7 @@ private:
/// least one error.
class MultipleErrorCheckingCallbacks : public ClangdServer::Callbacks {
public:
void onDiagnosticsReady(PathRef File,
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics);
@@ -276,7 +276,7 @@ TEST_F(ClangdVFSTest, PropagatesContexts) {
mutable int Got;
} FS;
struct Callbacks : public ClangdServer::Callbacks {
void onDiagnosticsReady(PathRef File,
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diagnostics) override {
Got = Context::current().getExisting(Secret);
}
@@ -295,6 +295,23 @@ TEST_F(ClangdVFSTest, PropagatesContexts) {
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<Diag> 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 @@ struct bar { T x; };
// 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 @@ int main() { return 0; }
// 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 @@ int hello;
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 @@ int d;
public:
TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
void onDiagnosticsReady(PathRef File,
void onDiagnosticsReady(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diagnostics) override {
StringRef FileIndexStr = llvm::sys::path::stem(File);
ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
@@ -672,8 +689,7 @@ int d;
bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen);
Server.addDocument(FilePaths[FileIndex],
ShouldHaveErrors ? SourceContentsWithErrors
: SourceContentsWithoutErrors,
WantDiagnostics::Auto);
: SourceContentsWithoutErrors);
UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors);
};
@@ -775,7 +791,8 @@ TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
: StartSecondReparse(std::move(StartSecondReparse)) {}
void onDiagnosticsReady(PathRef, std::vector<Diag>) override {
void onDiagnosticsReady(PathRef, llvm::StringRef,
std::vector<Diag>) override {
++Count;
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
ASSERT_TRUE(Lock.owns_lock())

View File

@@ -1509,7 +1509,7 @@ TEST(CompletionTest, DocumentationFromChangedFileCrash) {
}
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 @@ TEST(CompletionTest, NonDocComments) {
// 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()));

View File

@@ -151,8 +151,8 @@ void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) {
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 @@ TEST(FileIndexTest, RebuildWithPreamble) {
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 @@ TEST(FileIndexTest, Relations) {
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;

View File

@@ -702,7 +702,8 @@ TEST(SemanticHighlighting, GeneratesHighlightsWhenFileChange) {
std::atomic<int> Count = {0};
void onHighlightingsReady(
PathRef File, std::vector<HighlightingToken> Highlightings) override {
PathRef File, llvm::StringRef Version,
std::vector<HighlightingToken> Highlightings) override {
++Count;
}
};

View File

@@ -13,8 +13,9 @@ namespace clang {
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");
}

View File

@@ -23,7 +23,9 @@ namespace clangd {
// 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<CodeCompleteResult>
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,

View File

@@ -41,7 +41,15 @@ using ::testing::Pointee;
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 @@ protected:
void updateWithCallback(TUScheduler &S, PathRef File,
llvm::StringRef Contents, WantDiagnostics WD,
llvm::unique_function<void()> 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<void()> 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<llvm::unique_function<void(PathRef File, std::vector<Diag>)>>
@@ -78,8 +93,8 @@ protected:
reportDiagnostics(File, AST.getDiagnostics(), Publish);
}
void onFailedAST(PathRef File, std::vector<Diag> Diags,
PublishFn Publish) override {
void onFailedAST(PathRef File, llvm::StringRef Version,
std::vector<Diag> Diags, PublishFn Publish) override {
reportDiagnostics(File, Diags, Publish);
}
@@ -244,7 +259,9 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
// 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 <A>", WantDiagnostics::Yes, [&]() {
auto Inputs = getInputs(Path, "#include <A>");
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 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
// 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 <B>"), WantDiagnostics::Yes);
Inputs.Contents = "#include <B>";
Inputs.Version = "B";
S.update(Path, Inputs, WantDiagnostics::Yes);
S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
[&](Expected<InputsAndPreamble> Pre) {
ASSERT_TRUE(bool(Pre));
assert(bool(Pre));
EXPECT_EQ(Pre->Preamble->Version, "A");
EXPECT_THAT(includes(Pre->Preamble),
ElementsAre("<A>"));
InconsistentReadDone.notify();
@@ -271,6 +290,7 @@ TEST_F(TUSchedulerTests, PreambleConsistency) {
S.runWithPreamble("ConsistentRead", Path, TUScheduler::Consistent,
[&](Expected<InputsAndPreamble> Pre) {
ASSERT_TRUE(bool(Pre));
EXPECT_EQ(Pre->Preamble->Version, "B");
EXPECT_THAT(includes(Pre->Preamble),
ElementsAre("<B>"));
++CallbackCount;
@@ -446,6 +466,7 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
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<Diag>) {
@@ -467,6 +488,8 @@ TEST_F(TUSchedulerTests, ManyUpdates) {
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<std::mutex> Lock(Mut);
++TotalASTReads;
@@ -769,9 +792,6 @@ TEST_F(TUSchedulerTests, Run) {
TEST_F(TUSchedulerTests, TUStatus) {
class CaptureTUStatus : public ClangdServer::Callbacks {
public:
void onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) override {}
void onFileUpdated(PathRef File, const TUStatus &Status) override {
std::lock_guard<std::mutex> Lock(Mutex);
AllStatus.push_back(Status);
@@ -793,7 +813,8 @@ TEST_F(TUSchedulerTests, TUStatus) {
// 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<std::vector<LocatedSymbol>> Result) {
ASSERT_TRUE((bool)Result);
@@ -804,9 +825,9 @@ TEST_F(TUSchedulerTests, TUStatus) {
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"),

View File

@@ -97,7 +97,7 @@ ParsedAST TestTU::build() const {
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 @@ SymbolSlab TestTU::headerSymbols() const {
std::unique_ptr<SymbolIndex> TestTU::index() const {
auto AST = build();
auto Idx = std::make_unique<FileIndex>(/*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);
}

View File

@@ -849,7 +849,8 @@ TEST(LocateSymbol, WithPreamble) {
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 @@ TEST(LocateSymbol, WithPreamble) {
// 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())),