[mlir:LSP] Add a quickfix code action for inserting expected-* diagnostic checks

This allows for automatically inserting expected checks for parser and verifier
diagnostics, which simplifies the workflow when building new dialect
constructs or extending existing ones.

Differential Revision: https://reviews.llvm.org/D130152
This commit is contained in:
River Riddle 2022-07-20 01:43:07 -07:00
parent ad98ef8be4
commit ed344c8877
8 changed files with 555 additions and 0 deletions

View file

@ -58,6 +58,16 @@ any generated diagnostics in-place.
![IMG](/mlir-lsp-server/diagnostics.png)
##### Automatically insert `expected-` diagnostic checks
MLIR provides
[infrastructure](https://mlir.llvm.org/docs/Diagnostics/#sourcemgr-diagnostic-verifier-handler)
for checking expected diagnostics, which is heavily utilized when defining IR
parsing and verification. The language server provides code actions for
automatically inserting the checks for diagnostics it knows about.
![IMG](/mlir-lsp-server/diagnostics_action.gif)
#### Code completion
The language server provides suggestions as you type, offering completions for

View file

@ -267,6 +267,10 @@ bool mlir::lsp::fromJSON(const llvm::json::Value &value,
documentSymbol->getBoolean("hierarchicalDocumentSymbolSupport"))
result.hierarchicalDocumentSymbol = *hierarchicalSupport;
}
if (auto *codeAction = textDocument->getObject("codeAction")) {
if (codeAction->getObject("codeActionLiteralSupport"))
result.codeActionStructure = true;
}
}
return true;
}
@ -398,6 +402,12 @@ raw_ostream &mlir::lsp::operator<<(raw_ostream &os, const Range &value) {
// Location
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value, Location &result,
llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
return o && o.map("uri", result.uri) && o.map("range", result.range);
}
llvm::json::Value mlir::lsp::toJSON(const Location &value) {
return llvm::json::Object{
{"uri", value.uri},
@ -581,6 +591,14 @@ bool mlir::lsp::fromJSON(const llvm::json::Value &value,
// DiagnosticRelatedInformation
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value,
DiagnosticRelatedInformation &result,
llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
return o && o.map("location", result.location) &&
o.map("message", result.message);
}
llvm::json::Value mlir::lsp::toJSON(const DiagnosticRelatedInformation &info) {
return llvm::json::Object{
{"location", info.location},
@ -607,6 +625,23 @@ llvm::json::Value mlir::lsp::toJSON(const Diagnostic &diag) {
return std::move(result);
}
bool mlir::lsp::fromJSON(const llvm::json::Value &value, Diagnostic &result,
llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
if (!o)
return false;
int severity = 0;
if (!mapOptOrNull(value, "severity", severity, path))
return false;
result.severity = (DiagnosticSeverity)severity;
return o.map("range", result.range) && o.map("message", result.message) &&
mapOptOrNull(value, "category", result.category, path) &&
mapOptOrNull(value, "source", result.source, path) &&
mapOptOrNull(value, "relatedInformation", result.relatedInformation,
path);
}
//===----------------------------------------------------------------------===//
// PublishDiagnosticsParams
//===----------------------------------------------------------------------===//
@ -892,3 +927,65 @@ llvm::raw_ostream &mlir::lsp::operator<<(llvm::raw_ostream &os,
}
llvm_unreachable("Unknown InlayHintKind");
}
//===----------------------------------------------------------------------===//
// CodeActionContext
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value,
CodeActionContext &result, llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
if (!o || !o.map("diagnostics", result.diagnostics))
return false;
o.map("only", result.only);
return true;
}
//===----------------------------------------------------------------------===//
// CodeActionParams
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value,
CodeActionParams &result, llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
return o && o.map("textDocument", result.textDocument) &&
o.map("range", result.range) && o.map("context", result.context);
}
//===----------------------------------------------------------------------===//
// WorkspaceEdit
//===----------------------------------------------------------------------===//
bool mlir::lsp::fromJSON(const llvm::json::Value &value, WorkspaceEdit &result,
llvm::json::Path path) {
llvm::json::ObjectMapper o(value, path);
return o && o.map("changes", result.changes);
}
llvm::json::Value mlir::lsp::toJSON(const WorkspaceEdit &value) {
llvm::json::Object fileChanges;
for (auto &change : value.changes)
fileChanges[change.first] = llvm::json::Array(change.second);
return llvm::json::Object{{"changes", std::move(fileChanges)}};
}
//===----------------------------------------------------------------------===//
// CodeAction
//===----------------------------------------------------------------------===//
const llvm::StringLiteral CodeAction::kQuickFix = "quickfix";
const llvm::StringLiteral CodeAction::kRefactor = "refactor";
const llvm::StringLiteral CodeAction::kInfo = "info";
llvm::json::Value mlir::lsp::toJSON(const CodeAction &value) {
llvm::json::Object codeAction{{"title", value.title}};
if (value.kind)
codeAction["kind"] = *value.kind;
if (value.diagnostics)
codeAction["diagnostics"] = llvm::json::Array(*value.diagnostics);
if (value.isPreferred)
codeAction["isPreferred"] = true;
if (value.edit)
codeAction["edit"] = *value.edit;
return std::move(codeAction);
}

View file

@ -146,6 +146,10 @@ struct ClientCapabilities {
/// Client supports hierarchical document symbols.
/// textDocument.documentSymbol.hierarchicalDocumentSymbolSupport
bool hierarchicalDocumentSymbol = false;
/// Client supports CodeAction return value for textDocument/codeAction.
/// textDocument.codeAction.codeActionLiteralSupport.
bool codeActionStructure = false;
};
/// Add support for JSON serialization.
@ -374,6 +378,8 @@ struct Location {
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, Location &result,
llvm::json::Path path);
llvm::json::Value toJSON(const Location &value);
raw_ostream &operator<<(raw_ostream &os, const Location &value);
@ -612,6 +618,7 @@ bool fromJSON(const llvm::json::Value &value, DocumentSymbolParams &result,
/// 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() = default;
DiagnosticRelatedInformation(Location location, std::string message)
: location(std::move(location)), message(std::move(message)) {}
@ -622,6 +629,8 @@ struct DiagnosticRelatedInformation {
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value,
DiagnosticRelatedInformation &result, llvm::json::Path path);
llvm::json::Value toJSON(const DiagnosticRelatedInformation &info);
//===----------------------------------------------------------------------===//
@ -666,6 +675,8 @@ struct Diagnostic {
/// Add support for JSON serialization.
llvm::json::Value toJSON(const Diagnostic &diag);
bool fromJSON(const llvm::json::Value &value, Diagnostic &result,
llvm::json::Path path);
//===----------------------------------------------------------------------===//
// PublishDiagnosticsParams
@ -1086,6 +1097,103 @@ bool operator==(const InlayHint &lhs, const InlayHint &rhs);
bool operator<(const InlayHint &lhs, const InlayHint &rhs);
llvm::raw_ostream &operator<<(llvm::raw_ostream &os, InlayHintKind value);
//===----------------------------------------------------------------------===//
// CodeActionContext
//===----------------------------------------------------------------------===//
struct CodeActionContext {
/// An array of diagnostics known on the client side overlapping the range
/// provided to the `textDocument/codeAction` request. They are provided so
/// that the server knows which errors are currently presented to the user for
/// the given range. There is no guarantee that these accurately reflect the
/// error state of the resource. The primary parameter to compute code actions
/// is the provided range.
std::vector<Diagnostic> diagnostics;
/// Requested kind of actions to return.
///
/// Actions not of this kind are filtered out by the client before being
/// shown. So servers can omit computing them.
std::vector<std::string> only;
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, CodeActionContext &result,
llvm::json::Path path);
//===----------------------------------------------------------------------===//
// CodeActionParams
//===----------------------------------------------------------------------===//
struct CodeActionParams {
/// The document in which the command was invoked.
TextDocumentIdentifier textDocument;
/// The range for which the command was invoked.
Range range;
/// Context carrying additional information.
CodeActionContext context;
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, CodeActionParams &result,
llvm::json::Path path);
//===----------------------------------------------------------------------===//
// WorkspaceEdit
//===----------------------------------------------------------------------===//
struct WorkspaceEdit {
/// Holds changes to existing resources.
std::map<std::string, std::vector<TextEdit>> changes;
/// Note: "documentChanges" is not currently used because currently there is
/// no support for versioned edits.
};
/// Add support for JSON serialization.
bool fromJSON(const llvm::json::Value &value, WorkspaceEdit &result,
llvm::json::Path path);
llvm::json::Value toJSON(const WorkspaceEdit &value);
//===----------------------------------------------------------------------===//
// CodeAction
//===----------------------------------------------------------------------===//
/// A code action represents a change that can be performed in code, e.g. to fix
/// a problem or to refactor code.
///
/// A CodeAction must set either `edit` and/or a `command`. If both are
/// supplied, the `edit` is applied first, then the `command` is executed.
struct CodeAction {
/// A short, human-readable, title for this code action.
std::string title;
/// The kind of the code action.
/// Used to filter code actions.
Optional<std::string> kind;
const static llvm::StringLiteral kQuickFix;
const static llvm::StringLiteral kRefactor;
const static llvm::StringLiteral kInfo;
/// The diagnostics that this code action resolves.
Optional<std::vector<Diagnostic>> diagnostics;
/// Marks this as a preferred action. Preferred actions are used by the
/// `auto fix` command and can be targeted by keybindings.
/// A quick fix should be marked preferred if it properly addresses the
/// underlying error. A refactoring should be marked preferred if it is the
/// most reasonable choice of actions to take.
bool isPreferred = false;
/// The workspace edit this code action performs.
Optional<WorkspaceEdit> edit;
};
/// Add support for JSON serialization.
llvm::json::Value toJSON(const CodeAction &);
} // namespace lsp
} // namespace mlir

View file

@ -68,6 +68,12 @@ struct LSPServer {
void onCompletion(const CompletionParams &params,
Callback<CompletionList> reply);
//===--------------------------------------------------------------------===//
// Code Action
void onCodeAction(const CodeActionParams &params,
Callback<llvm::json::Value> reply);
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -121,6 +127,16 @@ void LSPServer::onInitialize(const InitializeParams &params,
params.capabilities.hierarchicalDocumentSymbol},
};
// Per LSP, codeActionProvider can be either boolean or CodeActionOptions.
// CodeActionOptions is only valid if the client supports action literal
// via textDocument.codeAction.codeActionLiteralSupport.
serverCaps["codeActionProvider"] =
params.capabilities.codeActionStructure
? llvm::json::Object{{"codeActionKinds",
{CodeAction::kQuickFix, CodeAction::kRefactor,
CodeAction::kInfo}}}
: llvm::json::Value(true);
llvm::json::Object result{
{{"serverInfo",
llvm::json::Object{{"name", "mlir-lsp-server"}, {"version", "0.0.0"}}},
@ -215,6 +231,29 @@ void LSPServer::onCompletion(const CompletionParams &params,
reply(server.getCodeCompletion(params.textDocument.uri, params.position));
}
//===----------------------------------------------------------------------===//
// Code Action
void LSPServer::onCodeAction(const CodeActionParams &params,
Callback<llvm::json::Value> reply) {
URIForFile uri = params.textDocument.uri;
// Check whether a particular CodeActionKind is included in the response.
auto isKindAllowed = [only(params.context.only)](StringRef kind) {
if (only.empty())
return true;
return llvm::any_of(only, [&](StringRef base) {
return kind.consume_front(base) && (kind.empty() || kind.startswith("."));
});
};
// We provide a code action for fixes on the specified diagnostics.
std::vector<CodeAction> actions;
if (isKindAllowed(CodeAction::kQuickFix))
server.getCodeActions(uri, params.range.start, params.context, actions);
reply(std::move(actions));
}
//===----------------------------------------------------------------------===//
// Entry point
//===----------------------------------------------------------------------===//
@ -255,6 +294,10 @@ LogicalResult lsp::runMlirLSPServer(MLIRServer &server,
messageHandler.method("textDocument/completion", &lspServer,
&LSPServer::onCompletion);
// Code Action
messageHandler.method("textDocument/codeAction", &lspServer,
&LSPServer::onCodeAction);
// Diagnostics
lspServer.publishDiagnostics =
messageHandler.outgoingNotification<PublishDiagnosticsParams>(

View file

@ -285,6 +285,15 @@ struct MLIRDocument {
const lsp::Position &completePos,
const DialectRegistry &registry);
//===--------------------------------------------------------------------===//
// Code Action
//===--------------------------------------------------------------------===//
void getCodeActionForDiagnostic(const lsp::URIForFile &uri,
lsp::Position &pos, StringRef severity,
StringRef message,
std::vector<lsp::TextEdit> &edits);
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -796,6 +805,42 @@ MLIRDocument::getCodeCompletion(const lsp::URIForFile &uri,
return completionList;
}
//===----------------------------------------------------------------------===//
// MLIRDocument: Code Action
//===----------------------------------------------------------------------===//
void MLIRDocument::getCodeActionForDiagnostic(
const lsp::URIForFile &uri, lsp::Position &pos, StringRef severity,
StringRef message, std::vector<lsp::TextEdit> &edits) {
// Ignore diagnostics that print the current operation. These are always
// enabled for the language server, but not generally during normal
// parsing/verification.
if (message.startswith("see current operation: "))
return;
// Get the start of the line containing the diagnostic.
const auto &buffer = sourceMgr.getBufferInfo(sourceMgr.getMainFileID());
const char *lineStart = buffer.getPointerForLineNumber(pos.line + 1);
if (!lineStart)
return;
StringRef line(lineStart, pos.character);
// Add a text edit for adding an expected-* diagnostic check for this
// diagnostic.
lsp::TextEdit edit;
edit.range = lsp::Range(lsp::Position(pos.line, 0));
// Use the indent of the current line for the expected-* diagnostic.
size_t indent = line.find_first_not_of(" ");
if (indent == StringRef::npos)
indent = line.size();
edit.newText.append(indent, ' ');
llvm::raw_string_ostream(edit.newText)
<< "// expected-" << severity << " @below {{" << message << "}}\n";
edits.emplace_back(std::move(edit));
}
//===----------------------------------------------------------------------===//
// MLIRTextFileChunk
//===----------------------------------------------------------------------===//
@ -853,6 +898,9 @@ public:
void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
lsp::Position completePos);
void getCodeActions(const lsp::URIForFile &uri, const lsp::Range &pos,
const lsp::CodeActionContext &context,
std::vector<lsp::CodeAction> &actions);
private:
/// Find the MLIR document that contains the given position, and update the
@ -1012,6 +1060,62 @@ lsp::CompletionList MLIRTextFile::getCodeCompletion(const lsp::URIForFile &uri,
return completionList;
}
void MLIRTextFile::getCodeActions(const lsp::URIForFile &uri,
const lsp::Range &pos,
const lsp::CodeActionContext &context,
std::vector<lsp::CodeAction> &actions) {
// Create actions for any diagnostics in this file.
for (auto &diag : context.diagnostics) {
if (diag.source != "mlir")
continue;
lsp::Position diagPos = diag.range.start;
MLIRTextFileChunk &chunk = getChunkFor(diagPos);
// Add a new code action that inserts a "expected" diagnostic check.
lsp::CodeAction action;
action.title = "Add expected-* diagnostic checks";
action.kind = lsp::CodeAction::kQuickFix.str();
StringRef severity;
switch (diag.severity) {
case lsp::DiagnosticSeverity::Error:
severity = "error";
break;
case lsp::DiagnosticSeverity::Warning:
severity = "warning";
break;
default:
continue;
}
// Get edits for the diagnostic.
std::vector<lsp::TextEdit> edits;
chunk.document.getCodeActionForDiagnostic(uri, diagPos, severity,
diag.message, edits);
// Walk the related diagnostics, this is how we encode notes.
if (diag.relatedInformation) {
for (auto &noteDiag : *diag.relatedInformation) {
if (noteDiag.location.uri != uri)
continue;
diagPos = noteDiag.location.range.start;
diagPos.line -= chunk.lineOffset;
chunk.document.getCodeActionForDiagnostic(uri, diagPos, "note",
noteDiag.message, edits);
}
}
// Fixup the locations for any edits.
for (lsp::TextEdit &edit : edits)
chunk.adjustLocForChunkOffset(edit.range);
action.edit.emplace();
action.edit->changes[uri.uri().str()] = std::move(edits);
action.diagnostics = {diag};
actions.emplace_back(std::move(action));
}
}
MLIRTextFileChunk &MLIRTextFile::getChunkFor(lsp::Position &pos) {
if (chunks.size() == 1)
return *chunks.front();
@ -1106,3 +1210,11 @@ lsp::MLIRServer::getCodeCompletion(const URIForFile &uri,
return fileIt->second->getCodeCompletion(uri, completePos);
return CompletionList();
}
void lsp::MLIRServer::getCodeActions(const URIForFile &uri, const Range &pos,
const CodeActionContext &context,
std::vector<CodeAction> &actions) {
auto fileIt = impl->files.find(uri.file());
if (fileIt != impl->files.end())
fileIt->second->getCodeActions(uri, pos, context, actions);
}

View file

@ -16,12 +16,15 @@ namespace mlir {
class DialectRegistry;
namespace lsp {
struct CodeAction;
struct CodeActionContext;
struct CompletionList;
struct Diagnostic;
struct DocumentSymbol;
struct Hover;
struct Location;
struct Position;
struct Range;
class URIForFile;
/// This class implements all of the MLIR related functionality necessary for a
@ -65,6 +68,11 @@ public:
CompletionList getCodeCompletion(const URIForFile &uri,
const Position &completePos);
/// Get the set of code actions within the file.
void getCodeActions(const URIForFile &uri, const Range &pos,
const CodeActionContext &context,
std::vector<CodeAction> &actions);
private:
struct Impl;

View file

@ -0,0 +1,176 @@
// 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":"#attr = 42 : f32\n// -----\nfunc.func @foo(%arg: i32) -> i64 {\nreturn %arg : i64\n}\n"
}}}
// -----
{"jsonrpc":"2.0","id":1,"method":"textDocument/codeAction","params":{
"textDocument":{
"uri":"file:///foo.mlir"
},
"range":{
"start":{"line":0,"character":8}, "end":{"line":0,"character":10}
},
"context":{
"diagnostics":[{
"range":{"start":{"line":0,"character":8}, "end":{"line":0,"character":10}},
"message":"unexpected decimal integer literal for a floating point value",
"severity":1,
"relatedInformation":[{
"message":"add a trailing dot to make the literal a float",
"location":{
"uri":"file:///foo.mlir",
"range":{"start":{"line":0,"character":8}, "end":{"line":0,"character":10}}
}
}],
"source":"mlir"
}],
"only":["quickfix"],
"triggerKind":1
}
}}
// CHECK-LABEL: "id": 1
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": [
// CHECK-NEXT: {
// CHECK-NEXT: "diagnostics": [
// CHECK-NEXT: {
// CHECK-NEXT: "message": "unexpected decimal integer literal for a floating point value",
// CHECK-NEXT: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 10,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 8,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "relatedInformation": [
// CHECK-NEXT: {
// CHECK-NEXT: "location": {
// CHECK-NEXT: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 10,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 8,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "uri": "file:///foo.mlir"
// CHECK-NEXT: },
// CHECK-NEXT: "message": "add a trailing dot to make the literal a float"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "severity": 1,
// CHECK-NEXT: "source": "mlir"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "edit": {
// CHECK-NEXT: "changes": {
// CHECK-NEXT: "file:///foo.mlir": [
// CHECK-NEXT: {
// CHECK-LITERAL: "newText": "// expected-error @below {{unexpected decimal integer literal for a floating point value}}\n"
// CHECK: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT-LITERAL: "newText": "// expected-note @below {{add a trailing dot to make the literal a float}}\n",
// CHECK: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 0
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "kind": "quickfix",
// CHECK-NEXT: "title": "Add expected-* diagnostic checks"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// -----
{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{
"textDocument":{"uri":"file:///foo.mlir"},
"range":{"start":{"line":3,"character":9},"end":{"line":3,"character":13}},
"context":{
"diagnostics":[{
"range":{"start":{"line":3,"character":9},"end":{"line":3,"character":13}},
"message":"use of value '%arg' expects different type than prior uses: 'i64' vs 'i32'",
"severity":1,
"relatedInformation":[{
"message":"prior use here",
"location":{
"uri":"file:///foo.mlir",
"range":{"start":{"line":2,"character":15},"end":{"line":2,"character":19}}
}
}],
"source":"mlir"
}],
"only":["quickfix"],
"triggerKind":1
}
}}
// CHECK-LABEL: "id": 2
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": [
// CHECK-NEXT: {
// CHECK: "edit": {
// CHECK-NEXT: "changes": {
// CHECK-NEXT: "file:///foo.mlir": [
// CHECK-NEXT: {
// CHECK-NEXT-LITERAL: "newText": "// expected-error @below {{use of value '%arg' expects different type than prior uses: 'i64' vs 'i32'}}\n",
// CHECK: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 3
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 3
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT-LITERAL: "newText": "// expected-note @below {{prior use here}}\n",
// CHECK: "range": {
// CHECK-NEXT: "end": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 2
// CHECK-NEXT: },
// CHECK-NEXT: "start": {
// CHECK-NEXT: "character": 0,
// CHECK-NEXT: "line": 2
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "kind": "quickfix",
// CHECK-NEXT: "title": "Add expected-* diagnostic checks"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// -----
{"jsonrpc":"2.0","id":10,"method":"shutdown"}
// -----
{"jsonrpc":"2.0","method":"exit"}

View file

@ -5,6 +5,7 @@
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": {
// CHECK-NEXT: "capabilities": {
// CHECK-NEXT: "codeActionProvider": true,
// CHECK-NEXT: "completionProvider": {
// CHECK-NEXT: "allCommitCharacters": [
// CHECK: ],