[clangd] Remove Tagged and some related APIs from ClangdServer.

Context can do what Tagged was intended to support (snapshot filesystems),
and less intrusively.
getTaggedFileSystem() no longer needs a filename.

Cleanups while here:
 - code-complete now returns errors as Expected, like other functions
 - added an alias Callback<T> for the usual callback function type

llvm-svn: 327344
This commit is contained in:
Sam McCall 2018-03-12 23:22:35 +00:00
parent 1527dec139
commit a7bb0cc09e
14 changed files with 199 additions and 296 deletions

View File

@ -330,28 +330,33 @@ void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
[](Tagged<CompletionList> List) { reply(List.Value); });
[](llvm::Expected<CompletionList> List) {
if (!List)
return replyError(ErrorCode::InvalidParams,
llvm::toString(List.takeError()));
reply(*List);
});
}
void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
[](llvm::Expected<Tagged<SignatureHelp>> SignatureHelp) {
[](llvm::Expected<SignatureHelp> SignatureHelp) {
if (!SignatureHelp)
return replyError(
ErrorCode::InvalidParams,
llvm::toString(SignatureHelp.takeError()));
reply(SignatureHelp->Value);
reply(*SignatureHelp);
});
}
void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Server.findDefinitions(
Params.textDocument.uri.file(), Params.position,
[](llvm::Expected<Tagged<std::vector<Location>>> Items) {
[](llvm::Expected<std::vector<Location>> Items) {
if (!Items)
return replyError(ErrorCode::InvalidParams,
llvm::toString(Items.takeError()));
reply(json::ary(Items->Value));
reply(json::ary(*Items));
});
}
@ -363,24 +368,24 @@ void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Server.findDocumentHighlights(
Params.textDocument.uri.file(), Params.position,
[](llvm::Expected<Tagged<std::vector<DocumentHighlight>>> Highlights) {
[](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
if (!Highlights)
return replyError(ErrorCode::InternalError,
llvm::toString(Highlights.takeError()));
reply(json::ary(Highlights->Value));
reply(json::ary(*Highlights));
});
}
void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
Server.findHover(Params.textDocument.uri.file(), Params.position,
[](llvm::Expected<Tagged<Hover>> H) {
[](llvm::Expected<Hover> H) {
if (!H) {
replyError(ErrorCode::InternalError,
llvm::toString(H.takeError()));
return;
}
reply(H->Value);
reply(*H);
});
}
@ -437,12 +442,12 @@ std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
return FixItsIter->second;
}
void ClangdLSPServer::onDiagnosticsReady(
PathRef File, Tagged<std::vector<Diag>> Diagnostics) {
void ClangdLSPServer::onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) {
json::ary DiagnosticsJSON;
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
for (auto &Diag : Diagnostics.Value) {
for (auto &Diag : Diagnostics) {
toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
DiagnosticsJSON.push_back(json::obj{
{"range", Diag.range},

View File

@ -46,8 +46,7 @@ public:
private:
// Implement DiagnosticsConsumer.
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override;
void onDiagnosticsReady(PathRef File, std::vector<Diag> Diagnostics) override;
// Implement ProtocolCallbacks.
void onInitialize(InitializeParams &Params) override;

View File

@ -65,9 +65,8 @@ public:
} // namespace
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
RealFileSystemProvider::getTaggedFileSystem(PathRef File) {
return make_tagged(vfs::getRealFileSystem(), VFSTag());
IntrusiveRefCntPtr<vfs::FileSystem> RealFileSystemProvider::getFileSystem() {
return vfs::getRealFileSystem();
}
ClangdServer::Options ClangdServer::optsForTest() {
@ -119,9 +118,8 @@ void ClangdServer::setRootPath(PathRef RootPath) {
void ClangdServer::addDocument(PathRef File, StringRef Contents,
WantDiagnostics WantDiags) {
DocVersion Version = DraftMgr.updateDraft(File, Contents);
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()},
WantDiags, std::move(TaggedFS));
WantDiags, FSProvider.getFileSystem());
}
void ClangdServer::removeDocument(PathRef File) {
@ -139,34 +137,29 @@ void ClangdServer::forceReparse(PathRef File) {
// remove any cahced flags.
CompileArgs.invalidate(File);
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
scheduleReparseAndDiags(File, std::move(FileContents), WantDiagnostics::Yes,
std::move(TaggedFS));
FSProvider.getFileSystem());
}
void ClangdServer::codeComplete(
PathRef File, Position Pos, const clangd::CodeCompleteOptions &Opts,
UniqueFunction<void(Tagged<CompletionList>)> Callback,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
using CallbackType = UniqueFunction<void(Tagged<CompletionList>)>;
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
if (UsedFS)
*UsedFS = TaggedFS.Value;
void ClangdServer::codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
Callback<CompletionList> CB) {
// Copy completion options for passing them to async task handler.
auto CodeCompleteOpts = Opts;
if (!CodeCompleteOpts.Index) // Respect overridden index.
CodeCompleteOpts.Index = Index;
VersionedDraft Latest = DraftMgr.getDraft(File);
// FIXME(sammccall): return error for consistency?
assert(Latest.Draft && "codeComplete called for non-added document");
if (!Latest.Draft)
return CB(llvm::make_error<llvm::StringError>(
"codeComplete called for non-added document",
llvm::errc::invalid_argument));
// Copy PCHs to avoid accessing this->PCHs concurrently
std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs;
auto Task = [PCHs, Pos, TaggedFS, CodeCompleteOpts](
std::string Contents, Path File, CallbackType Callback,
auto FS = FSProvider.getFileSystem();
auto Task = [PCHs, Pos, FS, CodeCompleteOpts](
std::string Contents, Path File, Callback<CompletionList> CB,
llvm::Expected<InputsAndPreamble> IP) {
assert(IP && "error when trying to read preamble for codeComplete");
auto PreambleData = IP->Preamble;
@ -176,49 +169,41 @@ void ClangdServer::codeComplete(
// both the old and the new version in case only one of them matches.
CompletionList Result = clangd::codeComplete(
File, Command, PreambleData ? &PreambleData->Preamble : nullptr,
Contents, Pos, TaggedFS.Value, PCHs, CodeCompleteOpts);
Callback(make_tagged(std::move(Result), std::move(TaggedFS.Tag)));
Contents, Pos, FS, PCHs, CodeCompleteOpts);
CB(std::move(Result));
};
WorkScheduler.runWithPreamble(
"CodeComplete", File,
Bind(Task, std::move(*Latest.Draft), File.str(), std::move(Callback)));
Bind(Task, std::move(*Latest.Draft), File.str(), std::move(CB)));
}
void ClangdServer::signatureHelp(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<SignatureHelp>>)> Callback,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
if (UsedFS)
*UsedFS = TaggedFS.Value;
void ClangdServer::signatureHelp(PathRef File, Position Pos,
Callback<SignatureHelp> CB) {
VersionedDraft Latest = DraftMgr.getDraft(File);
if (!Latest.Draft)
return Callback(llvm::make_error<llvm::StringError>(
return CB(llvm::make_error<llvm::StringError>(
"signatureHelp is called for non-added document",
llvm::errc::invalid_argument));
auto PCHs = this->PCHs;
auto Action = [Pos, TaggedFS, PCHs](std::string Contents, Path File,
decltype(Callback) Callback,
llvm::Expected<InputsAndPreamble> IP) {
auto FS = FSProvider.getFileSystem();
auto Action = [Pos, FS, PCHs](std::string Contents, Path File,
Callback<SignatureHelp> CB,
llvm::Expected<InputsAndPreamble> IP) {
if (!IP)
return Callback(IP.takeError());
return CB(IP.takeError());
auto PreambleData = IP->Preamble;
auto &Command = IP->Inputs.CompileCommand;
Callback(make_tagged(
clangd::signatureHelp(File, Command,
PreambleData ? &PreambleData->Preamble : nullptr,
Contents, Pos, TaggedFS.Value, PCHs),
TaggedFS.Tag));
CB(clangd::signatureHelp(File, Command,
PreambleData ? &PreambleData->Preamble : nullptr,
Contents, Pos, FS, PCHs));
};
WorkScheduler.runWithPreamble(
"SignatureHelp", File,
Bind(Action, std::move(*Latest.Draft), File.str(), std::move(Callback)));
Bind(Action, std::move(*Latest.Draft), File.str(), std::move(CB)));
}
llvm::Expected<tooling::Replacements>
@ -247,15 +232,13 @@ ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) {
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
}
void ClangdServer::rename(
PathRef File, Position Pos, llvm::StringRef NewName,
UniqueFunction<void(Expected<std::vector<tooling::Replacement>>)>
Callback) {
void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
Callback<std::vector<tooling::Replacement>> CB) {
auto Action = [Pos](Path File, std::string NewName,
decltype(Callback) Callback,
Callback<std::vector<tooling::Replacement>> CB,
Expected<InputsAndAST> InpAST) {
if (!InpAST)
return Callback(InpAST.takeError());
return CB(InpAST.takeError());
auto &AST = InpAST->AST;
RefactoringResultCollector ResultCollector;
@ -263,7 +246,7 @@ void ClangdServer::rename(
const FileEntry *FE =
SourceMgr.getFileEntryForID(SourceMgr.getMainFileID());
if (!FE)
return Callback(llvm::make_error<llvm::StringError>(
return CB(llvm::make_error<llvm::StringError>(
"rename called for non-added document",
llvm::errc::invalid_argument));
SourceLocation SourceLocationBeg =
@ -274,13 +257,13 @@ void ClangdServer::rename(
auto Rename = clang::tooling::RenameOccurrences::initiate(
Context, SourceRange(SourceLocationBeg), NewName);
if (!Rename)
return Callback(Rename.takeError());
return CB(Rename.takeError());
Rename->invoke(ResultCollector, Context);
assert(ResultCollector.Result.hasValue());
if (!ResultCollector.Result.getValue())
return Callback(ResultCollector.Result->takeError());
return CB(ResultCollector.Result->takeError());
std::vector<tooling::Replacement> Replacements;
for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
@ -299,12 +282,11 @@ void ClangdServer::rename(
Replacements.push_back(Rep);
}
}
return Callback(Replacements);
return CB(std::move(Replacements));
};
WorkScheduler.runWithAST(
"Rename", File,
Bind(Action, File.str(), NewName.str(), std::move(Callback)));
"Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB)));
}
/// Creates a `HeaderFile` from \p Header which can be either a URI or a literal
@ -335,9 +317,9 @@ ClangdServer::insertInclude(PathRef File, StringRef Code,
if (!ResolvedPreferred)
return ResolvedPreferred.takeError();
tooling::CompileCommand CompileCommand = CompileArgs.getCompileCommand(File);
auto Include = calculateIncludePath(
File, Code, *ResolvedOrginal, *ResolvedPreferred, CompileCommand,
FSProvider.getTaggedFileSystem(File).Value);
auto Include =
calculateIncludePath(File, Code, *ResolvedOrginal, *ResolvedPreferred,
CompileCommand, FSProvider.getFileSystem());
if (!Include)
return Include.takeError();
if (Include->empty())
@ -387,21 +369,17 @@ void ClangdServer::dumpAST(PathRef File,
WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback)));
}
void ClangdServer::findDefinitions(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<std::vector<Location>>>)>
Callback) {
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
llvm::Expected<InputsAndAST> InpAST) {
void ClangdServer::findDefinitions(PathRef File, Position Pos,
Callback<std::vector<Location>> CB) {
auto FS = FSProvider.getFileSystem();
auto Action = [Pos, FS](Callback<std::vector<Location>> CB,
llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return Callback(InpAST.takeError());
auto Result = clangd::findDefinitions(InpAST->AST, Pos);
Callback(make_tagged(std::move(Result), TaggedFS.Tag));
return CB(InpAST.takeError());
CB(clangd::findDefinitions(InpAST->AST, Pos));
};
WorkScheduler.runWithAST("Definitions", File,
Bind(Action, std::move(Callback)));
WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB)));
}
llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
@ -444,7 +422,7 @@ llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
SmallString<128> NewPath = StringRef(Path);
// Instance of vfs::FileSystem, used for file existence checks.
auto FS = FSProvider.getTaggedFileSystem(Path).Value;
auto FS = FSProvider.getFileSystem();
// Loop through switched extension candidates.
for (StringRef NewExt : NewExts) {
@ -467,9 +445,8 @@ llvm::Expected<tooling::Replacements>
ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
ArrayRef<tooling::Range> Ranges) {
// Call clang-format.
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
auto Style =
format::getStyle("file", File, "LLVM", Code, TaggedFS.Value.get());
auto FS = FSProvider.getFileSystem();
auto Style = format::getStyle("file", File, "LLVM", Code, FS.get());
if (!Style)
return Style.takeError();
@ -486,62 +463,51 @@ ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
}
void ClangdServer::findDocumentHighlights(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
Callback) {
PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
auto FileContents = DraftMgr.getDraft(File);
if (!FileContents.Draft)
return Callback(llvm::make_error<llvm::StringError>(
return CB(llvm::make_error<llvm::StringError>(
"findDocumentHighlights called on non-added file",
llvm::errc::invalid_argument));
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
auto Action = [TaggedFS, Pos](decltype(Callback) Callback,
llvm::Expected<InputsAndAST> InpAST) {
auto FS = FSProvider.getFileSystem();
auto Action = [FS, Pos](Callback<std::vector<DocumentHighlight>> CB,
llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return Callback(InpAST.takeError());
auto Result = clangd::findDocumentHighlights(InpAST->AST, Pos);
Callback(make_tagged(std::move(Result), TaggedFS.Tag));
return CB(InpAST.takeError());
CB(clangd::findDocumentHighlights(InpAST->AST, Pos));
};
WorkScheduler.runWithAST("Highlights", File,
Bind(Action, std::move(Callback)));
WorkScheduler.runWithAST("Highlights", File, Bind(Action, std::move(CB)));
}
void ClangdServer::findHover(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback) {
void ClangdServer::findHover(PathRef File, Position Pos, Callback<Hover> CB) {
Hover FinalHover;
auto FileContents = DraftMgr.getDraft(File);
if (!FileContents.Draft)
return Callback(llvm::make_error<llvm::StringError>(
return CB(llvm::make_error<llvm::StringError>(
"findHover called on non-added file", llvm::errc::invalid_argument));
auto TaggedFS = FSProvider.getTaggedFileSystem(File);
auto Action = [Pos, TaggedFS](decltype(Callback) Callback,
llvm::Expected<InputsAndAST> InpAST) {
auto FS = FSProvider.getFileSystem();
auto Action = [Pos, FS](Callback<Hover> CB,
llvm::Expected<InputsAndAST> InpAST) {
if (!InpAST)
return Callback(InpAST.takeError());
Hover Result = clangd::getHover(InpAST->AST, Pos);
Callback(make_tagged(std::move(Result), TaggedFS.Tag));
return CB(InpAST.takeError());
CB(clangd::getHover(InpAST->AST, Pos));
};
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(Callback)));
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
}
void ClangdServer::scheduleReparseAndDiags(
PathRef File, VersionedDraft Contents, WantDiagnostics WantDiags,
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {
IntrusiveRefCntPtr<vfs::FileSystem> FS) {
tooling::CompileCommand Command = CompileArgs.getCompileCommand(File);
DocVersion Version = Contents.Version;
Path FileStr = File.str();
VFSTag Tag = std::move(TaggedFS.Tag);
auto Callback = [this, Version, FileStr, Tag](std::vector<Diag> Diags) {
auto Callback = [this, Version, FileStr](std::vector<Diag> Diags) {
// We need to serialize access to resulting diagnostics to avoid calling
// `onDiagnosticsReady` in the wrong order.
std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
@ -554,13 +520,11 @@ void ClangdServer::scheduleReparseAndDiags(
return;
LastReportedDiagsVersion = Version;
DiagConsumer.onDiagnosticsReady(
FileStr, make_tagged(std::move(Diags), std::move(Tag)));
DiagConsumer.onDiagnosticsReady(FileStr, std::move(Diags));
};
WorkScheduler.update(File,
ParseInputs{std::move(Command),
std::move(TaggedFS.Value),
ParseInputs{std::move(Command), std::move(FS),
std::move(*Contents.Draft)},
WantDiags, std::move(Callback));
}

View File

@ -35,63 +35,29 @@ class PCHContainerOperations;
namespace clangd {
/// A tag supplied by the FileSytemProvider.
typedef std::string VFSTag;
/// A value of an arbitrary type and VFSTag that was supplied by the
/// FileSystemProvider when this value was computed.
template <class T> class Tagged {
public:
// MSVC requires future<> arguments to be default-constructible.
Tagged() = default;
template <class U>
Tagged(U &&Value, VFSTag Tag)
: Value(std::forward<U>(Value)), Tag(std::move(Tag)) {}
template <class U>
Tagged(const Tagged<U> &Other) : Value(Other.Value), Tag(Other.Tag) {}
template <class U>
Tagged(Tagged<U> &&Other)
: Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {}
T Value = T();
VFSTag Tag = VFSTag();
};
template <class T>
Tagged<typename std::decay<T>::type> make_tagged(T &&Value, VFSTag Tag) {
return Tagged<typename std::decay<T>::type>(std::forward<T>(Value), Tag);
}
class DiagnosticsConsumer {
public:
virtual ~DiagnosticsConsumer() = default;
/// Called by ClangdServer when \p Diagnostics for \p File are ready.
virtual void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) = 0;
std::vector<Diag> Diagnostics) = 0;
};
class FileSystemProvider {
public:
virtual ~FileSystemProvider() = default;
/// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing.
/// Name of the file that will be parsed is passed in \p File.
///
/// \return A filesystem that will be used for all file accesses in clangd.
/// A Tag returned by this method will be propagated to all results of clangd
/// that will use this filesystem.
virtual Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
getTaggedFileSystem(PathRef File) = 0;
/// Context::current() will be the context passed to the clang entrypoint,
/// such as addDocument(), and will also be propagated to result callbacks.
/// Embedders may use this to isolate filesystem accesses.
virtual IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() = 0;
};
class RealFileSystemProvider : public FileSystemProvider {
public:
/// \return getRealFileSystem() tagged with default tag, i.e. VFSTag()
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
getTaggedFileSystem(PathRef File) override;
/// Returns getRealFileSystem().
IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override;
};
/// Provides API to manage ASTs for a collection of C++ files and request
@ -137,10 +103,6 @@ public:
/// those arguments for subsequent reparses. However, ClangdServer will check
/// if compilation arguments changed on calls to forceReparse().
///
/// FSProvider provides a vfs::FileSystem for each parsing request. Results of
/// code completion and diagnostics also include a tag, that \p FSProvider
/// returns along with the vfs::FileSystem.
///
/// After each parsing request finishes, ClangdServer reports diagnostics to
/// \p DiagConsumer. Note that a callback to \p DiagConsumer happens on a
/// worker thread. Therefore, instances of \p DiagConsumer must properly
@ -186,39 +148,29 @@ public:
/// when codeComplete results become available.
void codeComplete(PathRef File, Position Pos,
const clangd::CodeCompleteOptions &Opts,
UniqueFunction<void(Tagged<CompletionList>)> Callback,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
Callback<CompletionList> CB);
/// Provide signature help for \p File at \p Pos. If \p OverridenContents is
/// not None, they will used only for signature help, i.e. no diagnostics
/// update will be scheduled and a draft for \p File will not be updated. If
/// If \p UsedFS is non-null, it will be overwritten by vfs::FileSystem used
/// for signature help. This method should only be called for tracked files.
void signatureHelp(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<SignatureHelp>>)> Callback,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
void signatureHelp(PathRef File, Position Pos, Callback<SignatureHelp> CB);
/// Get definition of symbol at a specified \p Line and \p Column in \p File.
void findDefinitions(
PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<std::vector<Location>>>)>
Callback);
void findDefinitions(PathRef File, Position Pos,
Callback<std::vector<Location>> CB);
/// Helper function that returns a path to the corresponding source file when
/// given a header file and vice versa.
llvm::Optional<Path> switchSourceHeader(PathRef Path);
/// Get document highlights for a given position.
void findDocumentHighlights(
PathRef File, Position Pos,
UniqueFunction<
void(llvm::Expected<Tagged<std::vector<DocumentHighlight>>>)>
Callback);
void findDocumentHighlights(PathRef File, Position Pos,
Callback<std::vector<DocumentHighlight>> CB);
/// Get code hover for a given position.
void findHover(PathRef File, Position Pos,
UniqueFunction<void(llvm::Expected<Tagged<Hover>>)> Callback);
void findHover(PathRef File, Position Pos, Callback<Hover> CB);
/// Run formatting for \p Rng inside \p File with content \p Code.
llvm::Expected<tooling::Replacements> formatRange(StringRef Code,
@ -236,8 +188,7 @@ public:
/// Rename all occurrences of the symbol at the \p Pos in \p File to
/// \p NewName.
void rename(PathRef File, Position Pos, llvm::StringRef NewName,
UniqueFunction<void(Expected<std::vector<tooling::Replacement>>)>
Callback);
Callback<std::vector<tooling::Replacement>> CB);
/// Inserts a new #include into \p File, if it's not present in \p Code.
///
@ -289,10 +240,9 @@ private:
formatCode(llvm::StringRef Code, PathRef File,
ArrayRef<tooling::Range> Ranges);
void
scheduleReparseAndDiags(PathRef File, VersionedDraft Contents,
WantDiagnostics WD,
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS);
void scheduleReparseAndDiags(PathRef File, VersionedDraft Contents,
WantDiagnostics WD,
IntrusiveRefCntPtr<vfs::FileSystem> FS);
CompileArgsCache CompileArgs;
DiagnosticsConsumer &DiagConsumer;

View File

@ -15,6 +15,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include <cassert>
#include <memory>
#include <tuple>
@ -27,6 +28,9 @@ namespace clangd {
/// A move-only type-erasing function wrapper. Similar to `std::function`, but
/// allows to store move-only callables.
template <class> class UniqueFunction;
/// A Callback<T> is a void function that accepts Expected<T>.
/// This is accepted by ClangdServer functions that logically return T.
template <typename T> using Callback = UniqueFunction<void(llvm::Expected<T>)>;
template <class Ret, class... Args> class UniqueFunction<Ret(Args...)> {
public:

View File

@ -76,16 +76,15 @@ public:
/// If an error occurs during processing, it is forwarded to the \p Action
/// callback.
void runWithAST(llvm::StringRef Name, PathRef File,
UniqueFunction<void(llvm::Expected<InputsAndAST>)> Action);
Callback<InputsAndAST> Action);
/// Schedule an async read of the Preamble. Preamble passed to \p Action may
/// be built for any version of the file, callers must not rely on it being
/// consistent with the current version of the file.
/// If an error occurs during processing, it is forwarded to the \p Action
/// callback.
void runWithPreamble(
llvm::StringRef Name, PathRef File,
UniqueFunction<void(llvm::Expected<InputsAndPreamble>)> Action);
void runWithPreamble(llvm::StringRef Name, PathRef File,
Callback<InputsAndPreamble> Action);
/// Wait until there are no scheduled or running tasks.
/// Mostly useful for synchronizing tests.

View File

@ -54,12 +54,10 @@ static bool diagsContainErrors(const std::vector<Diag> &Diagnostics) {
class ErrorCheckingDiagConsumer : public DiagnosticsConsumer {
public:
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics.Value);
std::vector<Diag> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
HadErrorInLastDiags = HadError;
LastVFSTag = Diagnostics.Tag;
}
bool hadErrorInLastDiags() {
@ -67,12 +65,9 @@ public:
return HadErrorInLastDiags;
}
VFSTag lastVFSTag() { return LastVFSTag; }
private:
std::mutex Mutex;
bool HadErrorInLastDiags = false;
VFSTag LastVFSTag = VFSTag();
};
/// For each file, record whether the last published diagnostics contained at
@ -80,8 +75,8 @@ private:
class MultipleErrorCheckingDiagConsumer : public DiagnosticsConsumer {
public:
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics.Value);
std::vector<Diag> Diagnostics) override {
bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
LastDiagsHadError[File] = HadError;
@ -149,7 +144,6 @@ protected:
FS.Files[testPath(FileWithContents.first)] = FileWithContents.second;
auto SourceFilename = testPath(SourceFileRelPath);
FS.ExpectedFile = SourceFilename;
Server.addDocument(SourceFilename, SourceContents);
auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename);
EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics";
@ -208,7 +202,6 @@ int b = a;
FS.Files[testPath("foo.h")] = "int a;";
FS.Files[FooCpp] = SourceContents;
FS.ExpectedFile = FooCpp;
Server.addDocument(FooCpp, SourceContents);
auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
@ -245,7 +238,6 @@ int b = a;
FS.Files[FooH] = "int a;";
FS.Files[FooCpp] = SourceContents;
FS.ExpectedFile = FooCpp;
Server.addDocument(FooCpp, SourceContents);
auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp);
@ -268,29 +260,33 @@ int b = a;
EXPECT_NE(DumpParse1, DumpParseDifferent);
}
TEST_F(ClangdVFSTest, CheckVersions) {
MockFSProvider FS;
ErrorCheckingDiagConsumer DiagConsumer;
TEST_F(ClangdVFSTest, PropagatesContexts) {
static Key<int> Secret;
struct FSProvider : public FileSystemProvider {
IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
Got = Context::current().getExisting(Secret);
return buildTestFS({});
}
int Got;
} FS;
struct DiagConsumer : public DiagnosticsConsumer {
void onDiagnosticsReady(PathRef File,
std::vector<Diag> Diagnostics) override {
Got = Context::current().getExisting(Secret);
}
int Got;
} DiagConsumer;
MockCompilationDatabase CDB;
// Verify that the context is plumbed to the FS provider and diagnostics.
ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
auto FooCpp = testPath("foo.cpp");
const auto SourceContents = "int a;";
FS.Files[FooCpp] = SourceContents;
FS.ExpectedFile = FooCpp;
// Use default completion options.
clangd::CodeCompleteOptions CCOpts;
FS.Tag = "123";
runAddDocument(Server, FooCpp, SourceContents);
EXPECT_EQ(runCodeComplete(Server, FooCpp, Position(), CCOpts).Tag, FS.Tag);
EXPECT_EQ(DiagConsumer.lastVFSTag(), FS.Tag);
FS.Tag = "321";
runAddDocument(Server, FooCpp, SourceContents);
EXPECT_EQ(DiagConsumer.lastVFSTag(), FS.Tag);
EXPECT_EQ(runCodeComplete(Server, FooCpp, Position(), CCOpts).Tag, FS.Tag);
{
WithContextValue Entrypoint(Secret, 42);
Server.addDocument(testPath("foo.cpp"), "void main(){}");
}
ASSERT_TRUE(Server.blockUntilIdleForTest());
EXPECT_EQ(FS.Got, 42);
EXPECT_EQ(DiagConsumer.Got, 42);
}
// Only enable this test on Unix
@ -362,7 +358,6 @@ struct bar { T x; };
)cpp";
FS.Files[FooCpp] = "";
FS.ExpectedFile = FooCpp;
// First parse files in C mode and check they produce errors.
CDB.ExtraClangFlags = {"-xc"};
@ -405,7 +400,6 @@ this
int main() { return 0; }
)cpp";
FS.Files[FooCpp] = "";
FS.ExpectedFile = FooCpp;
// Parse with define, we expect to see the errors.
CDB.ExtraClangFlags = {"-DWITH_ERROR"};
@ -474,8 +468,8 @@ int hello;
auto Locations = runFindDefinitions(Server, FooCpp, FooSource.point());
EXPECT_TRUE(bool(Locations));
EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp},
FooSource.range("one")}));
EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
FooSource.range("one")}));
// Undefine MACRO, close baz.cpp.
CDB.ExtraClangFlags.clear();
@ -489,8 +483,8 @@ int hello;
Locations = runFindDefinitions(Server, FooCpp, FooSource.point());
EXPECT_TRUE(bool(Locations));
EXPECT_THAT(Locations->Value, ElementsAre(Location{URIForFile{FooCpp},
FooSource.range("two")}));
EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
FooSource.range("two")}));
}
TEST_F(ClangdVFSTest, MemoryUsage) {
@ -550,13 +544,13 @@ TEST_F(ClangdVFSTest, InvalidCompileCommand) {
EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name"));
// FIXME: codeComplete and signatureHelp should also return errors when they
// can't parse the file.
EXPECT_THAT(
runCodeComplete(Server, FooCpp, Position(), clangd::CodeCompleteOptions())
.Value.items,
IsEmpty());
EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(),
clangd::CodeCompleteOptions()))
.items,
IsEmpty());
auto SigHelp = runSignatureHelp(Server, FooCpp, Position());
ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error";
EXPECT_THAT(SigHelp->Value.signatures, IsEmpty());
EXPECT_THAT(SigHelp->signatures, IsEmpty());
}
class ClangdThreadingTest : public ClangdVFSTest {};
@ -609,13 +603,13 @@ int d;
TestDiagConsumer() : Stats(FilesCount, FileStat()) {}
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override {
std::vector<Diag> Diagnostics) override {
StringRef FileIndexStr = llvm::sys::path::stem(File);
ASSERT_TRUE(FileIndexStr.consume_front("Foo"));
unsigned long FileIndex = std::stoul(FileIndexStr.str());
bool HadError = diagsContainErrors(Diagnostics.Value);
bool HadError = diagsContainErrors(Diagnostics);
std::lock_guard<std::mutex> Lock(Mutex);
if (HadError)
@ -739,8 +733,8 @@ int d;
// requests as opposed to AddDocument/RemoveDocument, which are implicitly
// cancelled by any subsequent AddDocument/RemoveDocument request to the
// same file.
runCodeComplete(Server, FilePaths[FileIndex], Pos,
clangd::CodeCompleteOptions());
cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos,
clangd::CodeCompleteOptions()));
};
auto FindDefinitionsRequest = [&]() {
@ -876,7 +870,7 @@ TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
NoConcurrentAccessDiagConsumer(std::promise<void> StartSecondReparse)
: StartSecondReparse(std::move(StartSecondReparse)) {}
void onDiagnosticsReady(PathRef, Tagged<std::vector<Diag>>) override {
void onDiagnosticsReady(PathRef, std::vector<Diag>) override {
++Count;
std::unique_lock<std::mutex> Lock(Mutex, std::try_to_lock_t());
ASSERT_TRUE(Lock.owns_lock())

View File

@ -62,7 +62,7 @@ using ::testing::UnorderedElementsAre;
class IgnoreDiagnostics : public DiagnosticsConsumer {
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override {}
std::vector<Diag> Diagnostics) override {}
};
// GMock helpers for matching completion items.
@ -121,7 +121,8 @@ CompletionList completions(StringRef Text,
auto File = testPath("foo.cpp");
Annotations Test(Text);
runAddDocument(Server, File, Test.code());
auto CompletionList = runCodeComplete(Server, File, Test.point(), Opts).Value;
auto CompletionList =
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
// Sanity-check that filterText is valid.
EXPECT_THAT(CompletionList.items, Each(NameContainsFilter()));
return CompletionList;
@ -536,15 +537,16 @@ TEST(CompletionTest, IndexSuppressesPreambleCompletions) {
auto I = memIndex({var("ns::index")});
Opts.Index = I.get();
auto WithIndex = runCodeComplete(Server, File, Test.point(), Opts).Value;
auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts));
EXPECT_THAT(WithIndex.items,
UnorderedElementsAre(Named("local"), Named("index")));
auto ClassFromPreamble =
runCodeComplete(Server, File, Test.point("2"), Opts).Value;
cantFail(runCodeComplete(Server, File, Test.point("2"), Opts));
EXPECT_THAT(ClassFromPreamble.items, Contains(Named("member")));
Opts.Index = nullptr;
auto WithoutIndex = runCodeComplete(Server, File, Test.point(), Opts).Value;
auto WithoutIndex =
cantFail(runCodeComplete(Server, File, Test.point(), Opts));
EXPECT_THAT(WithoutIndex.items,
UnorderedElementsAre(Named("local"), Named("preamble")));
}
@ -575,7 +577,7 @@ TEST(CompletionTest, DynamicIndexMultiFile) {
)cpp");
runAddDocument(Server, File, Test.code());
auto Results = runCodeComplete(Server, File, Test.point(), {}).Value;
auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {}));
// "XYZ" and "foo" are not included in the file being completed but are still
// visible through the index.
EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class));
@ -614,9 +616,7 @@ SignatureHelp signatures(StringRef Text) {
auto File = testPath("foo.cpp");
Annotations Test(Text);
runAddDocument(Server, File, Test.code());
auto R = runSignatureHelp(Server, File, Test.point());
assert(R);
return R.get().Value;
return cantFail(runSignatureHelp(Server, File, Test.point()));
}
MATCHER_P(ParamsAre, P, "") {

View File

@ -29,7 +29,7 @@ protected:
bool ExpectError = false) {
if (Preferred.empty())
Preferred = Original;
auto VFS = FS.getTaggedFileSystem(MainFile).Value;
auto VFS = FS.getFileSystem();
auto Cmd = CDB.getCompileCommand(MainFile);
assert(static_cast<bool>(Cmd));
VFS->setCurrentWorkingDirectory(Cmd->Directory);

View File

@ -67,31 +67,31 @@ template <typename T> CaptureProxy<T> capture(llvm::Optional<T> &Target) {
}
} // namespace
Tagged<CompletionList> runCodeComplete(ClangdServer &Server, PathRef File,
Position Pos,
clangd::CodeCompleteOptions Opts) {
llvm::Optional<Tagged<CompletionList>> Result;
llvm::Expected<CompletionList>
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
clangd::CodeCompleteOptions Opts) {
llvm::Optional<llvm::Expected<CompletionList>> Result;
Server.codeComplete(File, Pos, Opts, capture(Result));
return std::move(*Result);
}
llvm::Expected<Tagged<SignatureHelp>>
runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos) {
llvm::Optional<llvm::Expected<Tagged<SignatureHelp>>> Result;
llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server,
PathRef File, Position Pos) {
llvm::Optional<llvm::Expected<SignatureHelp>> Result;
Server.signatureHelp(File, Pos, capture(Result));
return std::move(*Result);
}
llvm::Expected<Tagged<std::vector<Location>>>
llvm::Expected<std::vector<Location>>
runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos) {
llvm::Optional<llvm::Expected<Tagged<std::vector<Location>>>> Result;
llvm::Optional<llvm::Expected<std::vector<Location>>> Result;
Server.findDefinitions(File, Pos, capture(Result));
return std::move(*Result);
}
llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
llvm::Expected<std::vector<DocumentHighlight>>
runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos) {
llvm::Optional<llvm::Expected<Tagged<std::vector<DocumentHighlight>>>> Result;
llvm::Optional<llvm::Expected<std::vector<DocumentHighlight>>> Result;
Server.findDocumentHighlights(File, Pos, capture(Result));
return std::move(*Result);
}

View File

@ -21,17 +21,17 @@ namespace clangd {
// Calls addDocument and then blockUntilIdleForTest.
void runAddDocument(ClangdServer &Server, PathRef File, StringRef Contents);
Tagged<CompletionList> runCodeComplete(ClangdServer &Server, PathRef File,
Position Pos,
clangd::CodeCompleteOptions Opts);
llvm::Expected<CompletionList>
runCodeComplete(ClangdServer &Server, PathRef File, Position Pos,
clangd::CodeCompleteOptions Opts);
llvm::Expected<Tagged<SignatureHelp>>
runSignatureHelp(ClangdServer &Server, PathRef File, Position Pos);
llvm::Expected<SignatureHelp> runSignatureHelp(ClangdServer &Server,
PathRef File, Position Pos);
llvm::Expected<Tagged<std::vector<Location>>>
llvm::Expected<std::vector<Location>>
runFindDefinitions(ClangdServer &Server, PathRef File, Position Pos);
llvm::Expected<Tagged<std::vector<DocumentHighlight>>>
llvm::Expected<std::vector<DocumentHighlight>>
runFindDocumentHighlights(ClangdServer &Server, PathRef File, Position Pos);
llvm::Expected<std::vector<tooling::Replacement>>

View File

@ -26,16 +26,6 @@ buildTestFS(StringMap<std::string> const &Files) {
return MemFS;
}
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
MockFSProvider::getTaggedFileSystem(PathRef File) {
if (ExpectedFile) {
EXPECT_EQ(*ExpectedFile, File);
}
auto FS = buildTestFS(Files);
return make_tagged(FS, Tag);
}
MockCompilationDatabase::MockCompilationDatabase(bool UseRelPaths)
: ExtraClangFlags({"-ffreestanding"}), UseRelPaths(UseRelPaths) {
// -ffreestanding avoids implicit stdc-predef.h.

View File

@ -26,13 +26,12 @@ buildTestFS(llvm::StringMap<std::string> const &Files);
// A VFS provider that returns TestFSes containing a provided set of files.
class MockFSProvider : public FileSystemProvider {
public:
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
getTaggedFileSystem(PathRef File) override;
IntrusiveRefCntPtr<vfs::FileSystem> getFileSystem() override {
return buildTestFS(Files);
}
llvm::Optional<std::string> ExpectedFile;
// If relative paths are used, they are resolved with testPath().
llvm::StringMap<std::string> Files;
VFSTag Tag = VFSTag();
};
// A Compilation database that returns a fixed set of compile flags.

View File

@ -42,7 +42,7 @@ using testing::UnorderedElementsAreArray;
class IgnoreDiagnostics : public DiagnosticsConsumer {
void onDiagnosticsReady(PathRef File,
Tagged<std::vector<Diag>> Diagnostics) override {}
std::vector<Diag> Diagnostics) override {}
};
// FIXME: this is duplicated with FileIndexTests. Share it.
@ -266,9 +266,8 @@ int baz = f^oo;
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
EXPECT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(
Locations->Value,
ElementsAre(Location{URIForFile{FooCpp}, SourceAnnotations.range()}));
EXPECT_THAT(*Locations, ElementsAre(Location{URIForFile{FooCpp},
SourceAnnotations.range()}));
}
TEST(Hover, All) {
@ -609,38 +608,38 @@ TEST(GoToInclude, All) {
auto Locations =
runFindDefinitions(Server, FooCpp, SourceAnnotations.point());
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include in preamble, last char.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("2"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("3"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test include outside of preamble.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("6"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value,
EXPECT_THAT(*Locations,
ElementsAre(Location{FooHUri, HeaderAnnotations.range()}));
// Test a few positions that do not result in Locations.
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("4"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("5"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
EXPECT_THAT(*Locations, IsEmpty());
Locations = runFindDefinitions(Server, FooCpp, SourceAnnotations.point("7"));
ASSERT_TRUE(bool(Locations)) << "findDefinitions returned an error";
EXPECT_THAT(Locations->Value, IsEmpty());
EXPECT_THAT(*Locations, IsEmpty());
}
} // namespace