[mlir-lsp-server] Add support for sending diagnostics to the client
This allows for diagnostics emitted during parsing/verification to be surfaced to the user by the language client, as opposed to just being emitted to the logs like they are now. Differential Revision: https://reviews.llvm.org/D102293
This commit is contained in:
parent
30b7dfafdb
commit
b3911cdfc8
|
@ -82,6 +82,7 @@ public:
|
|||
|
||||
AsmParserState();
|
||||
~AsmParserState();
|
||||
AsmParserState &operator=(AsmParserState &&other);
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Access State
|
||||
|
|
|
@ -52,6 +52,10 @@ struct AsmParserState::Impl {
|
|||
|
||||
AsmParserState::AsmParserState() : impl(std::make_unique<Impl>()) {}
|
||||
AsmParserState::~AsmParserState() {}
|
||||
AsmParserState &AsmParserState::operator=(AsmParserState &&other) {
|
||||
impl = std::move(other.impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Access State
|
||||
|
|
|
@ -59,6 +59,10 @@ struct LSPServer::Impl {
|
|||
MLIRServer &server;
|
||||
JSONTransport &transport;
|
||||
|
||||
/// An outgoing notification used to send diagnostics to the client when they
|
||||
/// are ready to be processed.
|
||||
OutgoingNotification<PublishDiagnosticsParams> publishDiagnostics;
|
||||
|
||||
/// Used to indicate that the 'shutdown' request was received from the
|
||||
/// Language Server client.
|
||||
bool shutdownRequestReceived = false;
|
||||
|
@ -99,11 +103,21 @@ void LSPServer::Impl::onShutdown(const NoParams &,
|
|||
|
||||
void LSPServer::Impl::onDocumentDidOpen(
|
||||
const DidOpenTextDocumentParams ¶ms) {
|
||||
server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text);
|
||||
PublishDiagnosticsParams diagParams(params.textDocument.uri);
|
||||
server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text,
|
||||
diagParams.diagnostics);
|
||||
|
||||
// Publish any recorded diagnostics.
|
||||
publishDiagnostics(diagParams);
|
||||
}
|
||||
void LSPServer::Impl::onDocumentDidClose(
|
||||
const DidCloseTextDocumentParams ¶ms) {
|
||||
server.removeDocument(params.textDocument.uri);
|
||||
|
||||
// Empty out the diagnostics shown for this document. This will clear out
|
||||
// anything currently displayed by the client for this document (e.g. in the
|
||||
// "Problems" pane of VSCode).
|
||||
publishDiagnostics(PublishDiagnosticsParams(params.textDocument.uri));
|
||||
}
|
||||
void LSPServer::Impl::onDocumentDidChange(
|
||||
const DidChangeTextDocumentParams ¶ms) {
|
||||
|
@ -111,8 +125,13 @@ void LSPServer::Impl::onDocumentDidChange(
|
|||
// to avoid this.
|
||||
if (params.contentChanges.size() != 1)
|
||||
return;
|
||||
PublishDiagnosticsParams diagParams(params.textDocument.uri);
|
||||
server.addOrUpdateDocument(params.textDocument.uri,
|
||||
params.contentChanges.front().text);
|
||||
params.contentChanges.front().text,
|
||||
diagParams.diagnostics);
|
||||
|
||||
// Publish any recorded diagnostics.
|
||||
publishDiagnostics(diagParams);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -173,6 +192,11 @@ LogicalResult LSPServer::run() {
|
|||
// Hover
|
||||
messageHandler.method("textDocument/hover", impl.get(), &Impl::onHover);
|
||||
|
||||
// Diagnostics
|
||||
impl->publishDiagnostics =
|
||||
messageHandler.outgoingNotification<PublishDiagnosticsParams>(
|
||||
"textDocument/publishDiagnostics");
|
||||
|
||||
// Run the main loop of the transport.
|
||||
LogicalResult result = success();
|
||||
if (llvm::Error error = impl->transport.run(messageHandler)) {
|
||||
|
|
|
@ -60,7 +60,28 @@ static Optional<lsp::Location> getLocationFromLoc(FileLineColLoc loc) {
|
|||
lsp::Position position;
|
||||
position.line = loc.getLine() - 1;
|
||||
position.character = loc.getColumn();
|
||||
return lsp::Location{*sourceURI, lsp::Range{position, position}};
|
||||
return lsp::Location{*sourceURI, lsp::Range(position)};
|
||||
}
|
||||
|
||||
/// Returns a language server location from the given MLIR location, or None if
|
||||
/// one couldn't be created. `uri` is an optional additional filter that, when
|
||||
/// present, is used to filter sub locations that do not share the same uri.
|
||||
static Optional<lsp::Location>
|
||||
getLocationFromLoc(Location loc, const lsp::URIForFile *uri = nullptr) {
|
||||
Optional<lsp::Location> location;
|
||||
loc->walk([&](Location nestedLoc) {
|
||||
FileLineColLoc fileLoc = nestedLoc.dyn_cast<FileLineColLoc>();
|
||||
if (!fileLoc)
|
||||
return WalkResult::advance();
|
||||
|
||||
Optional<lsp::Location> sourceLoc = getLocationFromLoc(fileLoc);
|
||||
if (sourceLoc && (!uri || sourceLoc->uri == *uri)) {
|
||||
location = *sourceLoc;
|
||||
return WalkResult::interrupt();
|
||||
}
|
||||
return WalkResult::advance();
|
||||
});
|
||||
return location;
|
||||
}
|
||||
|
||||
/// Collect all of the locations from the given MLIR location that are not
|
||||
|
@ -173,6 +194,56 @@ static void printDefBlockName(raw_ostream &os,
|
|||
printDefBlockName(os, def.block, def.definition.loc);
|
||||
}
|
||||
|
||||
/// Convert the given MLIR diagnostic to the LSP form.
|
||||
static lsp::Diagnostic getLspDiagnoticFromDiag(Diagnostic &diag,
|
||||
const lsp::URIForFile &uri) {
|
||||
lsp::Diagnostic lspDiag;
|
||||
lspDiag.source = "mlir";
|
||||
|
||||
// Note: Right now all of the diagnostics are treated as parser issues, but
|
||||
// some are parser and some are verifier.
|
||||
lspDiag.category = "Parse Error";
|
||||
|
||||
// Try to grab a file location for this diagnostic.
|
||||
// TODO: For simplicity, we just grab the first one. It may be likely that we
|
||||
// will need a more interesting heuristic here.'
|
||||
Optional<lsp::Location> lspLocation =
|
||||
getLocationFromLoc(diag.getLocation(), &uri);
|
||||
if (lspLocation)
|
||||
lspDiag.range = lspLocation->range;
|
||||
|
||||
// Convert the severity for the diagnostic.
|
||||
switch (diag.getSeverity()) {
|
||||
case DiagnosticSeverity::Note:
|
||||
llvm_unreachable("expected notes to be handled separately");
|
||||
case DiagnosticSeverity::Warning:
|
||||
lspDiag.severity = lsp::DiagnosticSeverity::Warning;
|
||||
break;
|
||||
case DiagnosticSeverity::Error:
|
||||
lspDiag.severity = lsp::DiagnosticSeverity::Error;
|
||||
break;
|
||||
case DiagnosticSeverity::Remark:
|
||||
lspDiag.severity = lsp::DiagnosticSeverity::Information;
|
||||
break;
|
||||
}
|
||||
lspDiag.message = diag.str();
|
||||
|
||||
// Attach any notes to the main diagnostic as related information.
|
||||
std::vector<lsp::DiagnosticRelatedInformation> relatedDiags;
|
||||
for (Diagnostic ¬e : diag.getNotes()) {
|
||||
lsp::Location noteLoc;
|
||||
if (Optional<lsp::Location> loc = getLocationFromLoc(note.getLocation()))
|
||||
noteLoc = *loc;
|
||||
else
|
||||
noteLoc.uri = uri;
|
||||
relatedDiags.emplace_back(noteLoc, note.str());
|
||||
}
|
||||
if (!relatedDiags.empty())
|
||||
lspDiag.relatedInformation = std::move(relatedDiags);
|
||||
|
||||
return lspDiag;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MLIRDocument
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -182,7 +253,8 @@ namespace {
|
|||
/// document.
|
||||
struct MLIRDocument {
|
||||
MLIRDocument(const lsp::URIForFile &uri, StringRef contents,
|
||||
DialectRegistry ®istry);
|
||||
DialectRegistry ®istry,
|
||||
std::vector<lsp::Diagnostic> &diagnostics);
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Definitions and References
|
||||
|
@ -227,15 +299,12 @@ struct MLIRDocument {
|
|||
} // namespace
|
||||
|
||||
MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents,
|
||||
DialectRegistry ®istry)
|
||||
DialectRegistry ®istry,
|
||||
std::vector<lsp::Diagnostic> &diagnostics)
|
||||
: context(registry) {
|
||||
context.allowUnregisteredDialects();
|
||||
ScopedDiagnosticHandler handler(&context, [&](Diagnostic &diag) {
|
||||
// TODO: What should we do with these diagnostics?
|
||||
// * Cache and show to the user?
|
||||
// * Ignore?
|
||||
lsp::Logger::error("Error when parsing MLIR document `{0}`: `{1}`",
|
||||
uri.file(), diag.str());
|
||||
diagnostics.push_back(getLspDiagnoticFromDiag(diag, uri));
|
||||
});
|
||||
|
||||
// Try to parsed the given IR string.
|
||||
|
@ -246,9 +315,13 @@ MLIRDocument::MLIRDocument(const lsp::URIForFile &uri, StringRef contents,
|
|||
}
|
||||
|
||||
sourceMgr.AddNewSourceBuffer(std::move(memBuffer), llvm::SMLoc());
|
||||
if (failed(
|
||||
parseSourceFile(sourceMgr, &parsedIR, &context, nullptr, &asmState)))
|
||||
if (failed(parseSourceFile(sourceMgr, &parsedIR, &context, nullptr,
|
||||
&asmState))) {
|
||||
// If parsing failed, clear out any of the current state.
|
||||
parsedIR.clear();
|
||||
asmState = AsmParserState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -495,10 +568,11 @@ lsp::MLIRServer::MLIRServer(DialectRegistry ®istry)
|
|||
: impl(std::make_unique<Impl>(registry)) {}
|
||||
lsp::MLIRServer::~MLIRServer() {}
|
||||
|
||||
void lsp::MLIRServer::addOrUpdateDocument(const URIForFile &uri,
|
||||
StringRef contents) {
|
||||
impl->documents[uri.file()] =
|
||||
std::make_unique<MLIRDocument>(uri, contents, impl->registry);
|
||||
void lsp::MLIRServer::addOrUpdateDocument(
|
||||
const URIForFile &uri, StringRef contents,
|
||||
std::vector<Diagnostic> &diagnostics) {
|
||||
impl->documents[uri.file()] = std::make_unique<MLIRDocument>(
|
||||
uri, contents, impl->registry, diagnostics);
|
||||
}
|
||||
|
||||
void lsp::MLIRServer::removeDocument(const URIForFile &uri) {
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace mlir {
|
|||
class DialectRegistry;
|
||||
|
||||
namespace lsp {
|
||||
struct Diagnostic;
|
||||
struct Hover;
|
||||
struct Location;
|
||||
struct Position;
|
||||
|
@ -30,8 +31,10 @@ public:
|
|||
MLIRServer(DialectRegistry ®istry);
|
||||
~MLIRServer();
|
||||
|
||||
/// Add or update the document at the given URI.
|
||||
void addOrUpdateDocument(const URIForFile &uri, StringRef contents);
|
||||
/// Add or update the document at the given URI. Any diagnostics emitted for
|
||||
/// this document should be added to `diagnostics`
|
||||
void addOrUpdateDocument(const URIForFile &uri, StringRef contents,
|
||||
std::vector<Diagnostic> &diagnostics);
|
||||
|
||||
/// Remove the document with the given uri.
|
||||
void removeDocument(const URIForFile &uri);
|
||||
|
|
|
@ -473,3 +473,44 @@ llvm::json::Value mlir::lsp::toJSON(const Hover &hover) {
|
|||
result["range"] = toJSON(*hover.range);
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// DiagnosticRelatedInformation
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
llvm::json::Value mlir::lsp::toJSON(const DiagnosticRelatedInformation &info) {
|
||||
return llvm::json::Object{
|
||||
{"location", info.location},
|
||||
{"message", info.message},
|
||||
};
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Diagnostic
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
llvm::json::Value mlir::lsp::toJSON(const Diagnostic &diag) {
|
||||
llvm::json::Object result{
|
||||
{"range", diag.range},
|
||||
{"severity", (int)diag.severity},
|
||||
{"message", diag.message},
|
||||
};
|
||||
if (diag.category)
|
||||
result["category"] = *diag.category;
|
||||
if (!diag.source.empty())
|
||||
result["source"] = diag.source;
|
||||
if (diag.relatedInformation)
|
||||
result["relatedInformation"] = *diag.relatedInformation;
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// PublishDiagnosticsParams
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
llvm::json::Value mlir::lsp::toJSON(const PublishDiagnosticsParams ¶ms) {
|
||||
return llvm::json::Object{
|
||||
{"uri", params.uri},
|
||||
{"diagnostics", params.diagnostics},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -228,6 +228,10 @@ raw_ostream &operator<<(raw_ostream &os, const Position &value);
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct Range {
|
||||
Range() = default;
|
||||
Range(Position start, Position end) : start(start), end(end) {}
|
||||
Range(Position loc) : Range(loc, loc) {}
|
||||
|
||||
/// The range's start position.
|
||||
Position start;
|
||||
|
||||
|
@ -393,6 +397,79 @@ struct Hover {
|
|||
};
|
||||
llvm::json::Value toJSON(const Hover &hover);
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// DiagnosticRelatedInformation
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Represents a related message and source code location for a diagnostic.
|
||||
/// This should be used to point to code locations that cause or related to a
|
||||
/// diagnostics, e.g. when duplicating a symbol in a scope.
|
||||
struct DiagnosticRelatedInformation {
|
||||
DiagnosticRelatedInformation(Location location, std::string message)
|
||||
: location(location), message(std::move(message)) {}
|
||||
|
||||
/// The location of this related diagnostic information.
|
||||
Location location;
|
||||
/// The message of this related diagnostic information.
|
||||
std::string message;
|
||||
};
|
||||
llvm::json::Value toJSON(const DiagnosticRelatedInformation &info);
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Diagnostic
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
enum class DiagnosticSeverity {
|
||||
/// It is up to the client to interpret diagnostics as error, warning, info or
|
||||
/// hint.
|
||||
Undetermined = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Information = 3,
|
||||
Hint = 4
|
||||
};
|
||||
|
||||
struct Diagnostic {
|
||||
/// The source range where the message applies.
|
||||
Range range;
|
||||
|
||||
/// The diagnostic's severity. Can be omitted. If omitted it is up to the
|
||||
/// client to interpret diagnostics as error, warning, info or hint.
|
||||
DiagnosticSeverity severity = DiagnosticSeverity::Undetermined;
|
||||
|
||||
/// A human-readable string describing the source of this diagnostic, e.g.
|
||||
/// 'typescript' or 'super lint'.
|
||||
std::string source;
|
||||
|
||||
/// The diagnostic's message.
|
||||
std::string message;
|
||||
|
||||
/// An array of related diagnostic information, e.g. when symbol-names within
|
||||
/// a scope collide all definitions can be marked via this property.
|
||||
Optional<std::vector<DiagnosticRelatedInformation>> relatedInformation;
|
||||
|
||||
/// The diagnostic's category. Can be omitted.
|
||||
/// An LSP extension that's used to send the name of the category over to the
|
||||
/// client. The category typically describes the compilation stage during
|
||||
/// which the issue was produced, e.g. "Semantic Issue" or "Parse Issue".
|
||||
Optional<std::string> category;
|
||||
};
|
||||
llvm::json::Value toJSON(const Diagnostic &diag);
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// PublishDiagnosticsParams
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct PublishDiagnosticsParams {
|
||||
PublishDiagnosticsParams(URIForFile uri) : uri(uri) {}
|
||||
|
||||
/// The URI for which diagnostic information is reported.
|
||||
URIForFile uri;
|
||||
/// The list of reported diagnostics.
|
||||
std::vector<Diagnostic> diagnostics;
|
||||
};
|
||||
llvm::json::Value toJSON(const PublishDiagnosticsParams ¶ms);
|
||||
|
||||
} // namespace lsp
|
||||
} // namespace mlir
|
||||
|
||||
|
|
|
@ -21,6 +21,30 @@ using namespace mlir::lsp;
|
|||
// Reply
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
/// Function object to reply to an LSP call.
|
||||
/// Each instance must be called exactly once, otherwise:
|
||||
/// - if there was no reply, an error reply is sent
|
||||
/// - if there were multiple replies, only the first is sent
|
||||
class Reply {
|
||||
public:
|
||||
Reply(const llvm::json::Value &id, StringRef method,
|
||||
JSONTransport &transport);
|
||||
Reply(Reply &&other);
|
||||
Reply &operator=(Reply &&) = delete;
|
||||
Reply(const Reply &) = delete;
|
||||
Reply &operator=(const Reply &) = delete;
|
||||
|
||||
void operator()(llvm::Expected<llvm::json::Value> reply);
|
||||
|
||||
private:
|
||||
StringRef method;
|
||||
std::atomic<bool> replied = {false};
|
||||
llvm::json::Value id;
|
||||
JSONTransport *transport;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Reply::Reply(const llvm::json::Value &id, llvm::StringRef method,
|
||||
JSONTransport &transport)
|
||||
: id(id), transport(&transport) {}
|
||||
|
|
|
@ -28,107 +28,7 @@
|
|||
|
||||
namespace mlir {
|
||||
namespace lsp {
|
||||
class JSONTransport;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Reply
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// Function object to reply to an LSP call.
|
||||
/// Each instance must be called exactly once, otherwise:
|
||||
/// - if there was no reply, an error reply is sent
|
||||
/// - if there were multiple replies, only the first is sent
|
||||
class Reply {
|
||||
public:
|
||||
Reply(const llvm::json::Value &id, StringRef method,
|
||||
JSONTransport &transport);
|
||||
Reply(Reply &&other);
|
||||
Reply &operator=(Reply &&) = delete;
|
||||
Reply(const Reply &) = delete;
|
||||
Reply &operator=(const Reply &) = delete;
|
||||
|
||||
void operator()(llvm::Expected<llvm::json::Value> reply);
|
||||
|
||||
private:
|
||||
StringRef method;
|
||||
std::atomic<bool> replied = {false};
|
||||
llvm::json::Value id;
|
||||
JSONTransport *transport;
|
||||
};
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MessageHandler
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// A Callback<T> is a void function that accepts Expected<T>. This is
|
||||
/// accepted by functions that logically return T.
|
||||
template <typename T>
|
||||
using Callback = llvm::unique_function<void(llvm::Expected<T>)>;
|
||||
|
||||
/// A handler used to process the incoming transport messages.
|
||||
class MessageHandler {
|
||||
public:
|
||||
MessageHandler(JSONTransport &transport) : transport(transport) {}
|
||||
|
||||
bool onNotify(StringRef method, llvm::json::Value value);
|
||||
bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id);
|
||||
bool onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
|
||||
|
||||
template <typename T>
|
||||
static llvm::Expected<T> parse(const llvm::json::Value &raw,
|
||||
StringRef payloadName, StringRef payloadKind) {
|
||||
T result;
|
||||
llvm::json::Path::Root root;
|
||||
if (fromJSON(raw, result, root))
|
||||
return std::move(result);
|
||||
|
||||
// Dump the relevant parts of the broken message.
|
||||
std::string context;
|
||||
llvm::raw_string_ostream os(context);
|
||||
root.printErrorContext(raw, os);
|
||||
|
||||
// Report the error (e.g. to the client).
|
||||
return llvm::make_error<LSPError>(
|
||||
llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind,
|
||||
fmt_consume(root.getError())),
|
||||
ErrorCode::InvalidParams);
|
||||
}
|
||||
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void method(llvm::StringLiteral method, ThisT *thisPtr,
|
||||
void (ThisT::*handler)(const Param &, Callback<Result>)) {
|
||||
methodHandlers[method] = [method, handler,
|
||||
thisPtr](llvm::json::Value rawParams,
|
||||
Callback<llvm::json::Value> reply) {
|
||||
llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
|
||||
if (!param)
|
||||
return reply(param.takeError());
|
||||
(thisPtr->*handler)(*param, std::move(reply));
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Param, typename ThisT>
|
||||
void notification(llvm::StringLiteral method, ThisT *thisPtr,
|
||||
void (ThisT::*handler)(const Param &)) {
|
||||
notificationHandlers[method] = [method, handler,
|
||||
thisPtr](llvm::json::Value rawParams) {
|
||||
llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
|
||||
if (!param)
|
||||
return llvm::consumeError(param.takeError());
|
||||
(thisPtr->*handler)(*param);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename HandlerT>
|
||||
using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
|
||||
|
||||
HandlerMap<void(llvm::json::Value)> notificationHandlers;
|
||||
HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)>
|
||||
methodHandlers;
|
||||
|
||||
JSONTransport &transport;
|
||||
};
|
||||
class MessageHandler;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// JSONTransport
|
||||
|
@ -185,6 +85,93 @@ private:
|
|||
bool prettyOutput;
|
||||
};
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MessageHandler
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// A Callback<T> is a void function that accepts Expected<T>. This is
|
||||
/// accepted by functions that logically return T.
|
||||
template <typename T>
|
||||
using Callback = llvm::unique_function<void(llvm::Expected<T>)>;
|
||||
|
||||
/// An OutgoingNotification<T> is a function used for outgoing notifications
|
||||
/// send to the client.
|
||||
template <typename T>
|
||||
using OutgoingNotification = llvm::unique_function<void(const T &)>;
|
||||
|
||||
/// A handler used to process the incoming transport messages.
|
||||
class MessageHandler {
|
||||
public:
|
||||
MessageHandler(JSONTransport &transport) : transport(transport) {}
|
||||
|
||||
bool onNotify(StringRef method, llvm::json::Value value);
|
||||
bool onCall(StringRef method, llvm::json::Value params, llvm::json::Value id);
|
||||
bool onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result);
|
||||
|
||||
template <typename T>
|
||||
static llvm::Expected<T> parse(const llvm::json::Value &raw,
|
||||
StringRef payloadName, StringRef payloadKind) {
|
||||
T result;
|
||||
llvm::json::Path::Root root;
|
||||
if (fromJSON(raw, result, root))
|
||||
return std::move(result);
|
||||
|
||||
// Dump the relevant parts of the broken message.
|
||||
std::string context;
|
||||
llvm::raw_string_ostream os(context);
|
||||
root.printErrorContext(raw, os);
|
||||
|
||||
// Report the error (e.g. to the client).
|
||||
return llvm::make_error<LSPError>(
|
||||
llvm::formatv("failed to decode {0} {1}: {2}", payloadName, payloadKind,
|
||||
fmt_consume(root.getError())),
|
||||
ErrorCode::InvalidParams);
|
||||
}
|
||||
|
||||
template <typename Param, typename Result, typename ThisT>
|
||||
void method(llvm::StringLiteral method, ThisT *thisPtr,
|
||||
void (ThisT::*handler)(const Param &, Callback<Result>)) {
|
||||
methodHandlers[method] = [method, handler,
|
||||
thisPtr](llvm::json::Value rawParams,
|
||||
Callback<llvm::json::Value> reply) {
|
||||
llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
|
||||
if (!param)
|
||||
return reply(param.takeError());
|
||||
(thisPtr->*handler)(*param, std::move(reply));
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Param, typename ThisT>
|
||||
void notification(llvm::StringLiteral method, ThisT *thisPtr,
|
||||
void (ThisT::*handler)(const Param &)) {
|
||||
notificationHandlers[method] = [method, handler,
|
||||
thisPtr](llvm::json::Value rawParams) {
|
||||
llvm::Expected<Param> param = parse<Param>(rawParams, method, "request");
|
||||
if (!param)
|
||||
return llvm::consumeError(param.takeError());
|
||||
(thisPtr->*handler)(*param);
|
||||
};
|
||||
}
|
||||
|
||||
/// Create an OutgoingNotification object used for the given method.
|
||||
template <typename T>
|
||||
OutgoingNotification<T> outgoingNotification(llvm::StringLiteral method) {
|
||||
return [&, method](const T ¶ms) {
|
||||
transport.notify(method, llvm::json::Value(params));
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename HandlerT>
|
||||
using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
|
||||
|
||||
HandlerMap<void(llvm::json::Value)> notificationHandlers;
|
||||
HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)>
|
||||
methodHandlers;
|
||||
|
||||
JSONTransport &transport;
|
||||
};
|
||||
|
||||
} // namespace lsp
|
||||
} // namespace mlir
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// RUN: mlir-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"mlir","capabilities":{},"trace":"off"}}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
|
||||
"uri":"test:///foo.mlir",
|
||||
"languageId":"mlir",
|
||||
"version":1,
|
||||
"text":"func ()"
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK-NEXT: "diagnostics": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "category": "Parse Error",
|
||||
// CHECK-NEXT: "message": "custom op 'func' expected valid '@'-identifier for symbol name",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "character": 6,
|
||||
// CHECK-NEXT: "line": 0
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "start": {
|
||||
// CHECK-NEXT: "character": 6,
|
||||
// CHECK-NEXT: "line": 0
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "severity": 1,
|
||||
// CHECK-NEXT: "source": "mlir"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "uri": "test:///foo.mlir"
|
||||
// CHECK-NEXT: }
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
Loading…
Reference in New Issue