[mlir][PDLL] Add signature help to the PDLL language server

This commit adds signature support to the language server,
and initially supports providing help for: operation operands and results,
and constraint/rewrite calls.

Differential Revision: https://reviews.llvm.org/D121545
This commit is contained in:
River Riddle 2022-03-11 00:38:17 -08:00
parent 008de486f7
commit 469c58944d
9 changed files with 468 additions and 0 deletions

View File

@ -66,6 +66,24 @@ public:
/// Signal code completion for Pattern metadata.
virtual void codeCompletePatternMetadata() {}
//===--------------------------------------------------------------------===//
// Signature Hooks
//===--------------------------------------------------------------------===//
/// Signal code completion for the signature of a callable.
virtual void codeCompleteCallSignature(const ast::CallableDecl *callable,
unsigned currentNumArgs) {}
/// Signal code completion for the signature of an operation's operands.
virtual void
codeCompleteOperationOperandsSignature(Optional<StringRef> opName,
unsigned currentNumOperands) {}
/// Signal code completion for the signature of an operation's results.
virtual void
codeCompleteOperationResultsSignature(Optional<StringRef> opName,
unsigned currentNumResults) {}
protected:
/// Create a new code completion context with the given code complete
/// location.

View File

@ -425,6 +425,12 @@ private:
LogicalResult codeCompleteOperationName(StringRef dialectName);
LogicalResult codeCompletePatternMetadata();
void codeCompleteCallSignature(ast::Node *parent, unsigned currentNumArgs);
void codeCompleteOperationOperandsSignature(Optional<StringRef> opName,
unsigned currentNumOperands);
void codeCompleteOperationResultsSignature(Optional<StringRef> opName,
unsigned currentNumResults);
//===--------------------------------------------------------------------===//
// Lexer Utilities
//===--------------------------------------------------------------------===//
@ -1762,6 +1768,12 @@ FailureOr<ast::Expr *> Parser::parseCallExpr(ast::Expr *parentExpr) {
SmallVector<ast::Expr *> arguments;
if (curToken.isNot(Token::r_paren)) {
do {
// Handle code completion for the call arguments.
if (curToken.is(Token::code_complete)) {
codeCompleteCallSignature(parentExpr, arguments.size());
return failure();
}
FailureOr<ast::Expr *> argument = parseExpr();
if (failed(argument))
return failure();
@ -1933,6 +1945,12 @@ FailureOr<ast::Expr *> Parser::parseOperationExpr() {
ast::ValueRangeConstraintDecl::create(ctx, loc), valueRangeTy));
}
} else if (!consumeIf(Token::r_paren)) {
// Check for operand signature code completion.
if (curToken.is(Token::code_complete)) {
codeCompleteOperationOperandsSignature(opName, operands.size());
return failure();
}
// If the operand list was specified and non-empty, parse the operands.
do {
FailureOr<ast::Expr *> operand = parseExpr();
@ -1972,6 +1990,12 @@ FailureOr<ast::Expr *> Parser::parseOperationExpr() {
// Handle the case of an empty result list.
if (!consumeIf(Token::r_paren)) {
do {
// Check for result signature code completion.
if (curToken.is(Token::code_complete)) {
codeCompleteOperationResultsSignature(opName, resultTypes.size());
return failure();
}
FailureOr<ast::Expr *> resultTypeExpr = parseExpr();
if (failed(resultTypeExpr))
return failure();
@ -2899,6 +2923,27 @@ LogicalResult Parser::codeCompletePatternMetadata() {
return failure();
}
void Parser::codeCompleteCallSignature(ast::Node *parent,
unsigned currentNumArgs) {
ast::CallableDecl *callableDecl = tryExtractCallableDecl(parent);
if (!callableDecl)
return;
codeCompleteContext->codeCompleteCallSignature(callableDecl, currentNumArgs);
}
void Parser::codeCompleteOperationOperandsSignature(
Optional<StringRef> opName, unsigned currentNumOperands) {
codeCompleteContext->codeCompleteOperationOperandsSignature(
opName, currentNumOperands);
}
void Parser::codeCompleteOperationResultsSignature(Optional<StringRef> opName,
unsigned currentNumResults) {
codeCompleteContext->codeCompleteOperationResultsSignature(opName,
currentNumResults);
}
//===----------------------------------------------------------------------===//
// Parser
//===----------------------------------------------------------------------===//

View File

@ -742,3 +742,57 @@ bool mlir::lsp::fromJSON(const llvm::json::Value &value,
return fromJSON(*context, result.context, path.field("context"));
return true;
}
//===----------------------------------------------------------------------===//
// ParameterInformation
//===----------------------------------------------------------------------===//
llvm::json::Value mlir::lsp::toJSON(const ParameterInformation &value) {
assert((value.labelOffsets.hasValue() || !value.labelString.empty()) &&
"parameter information label is required");
llvm::json::Object result;
if (value.labelOffsets)
result["label"] = llvm::json::Array(
{value.labelOffsets->first, value.labelOffsets->second});
else
result["label"] = value.labelString;
if (!value.documentation.empty())
result["documentation"] = value.documentation;
return std::move(result);
}
//===----------------------------------------------------------------------===//
// SignatureInformation
//===----------------------------------------------------------------------===//
llvm::json::Value mlir::lsp::toJSON(const SignatureInformation &value) {
assert(!value.label.empty() && "signature information label is required");
llvm::json::Object result{
{"label", value.label},
{"parameters", llvm::json::Array(value.parameters)},
};
if (!value.documentation.empty())
result["documentation"] = value.documentation;
return std::move(result);
}
raw_ostream &mlir::lsp::operator<<(raw_ostream &os,
const SignatureInformation &value) {
return os << value.label << " - " << toJSON(value);
}
//===----------------------------------------------------------------------===//
// SignatureHelp
//===----------------------------------------------------------------------===//
llvm::json::Value mlir::lsp::toJSON(const SignatureHelp &value) {
assert(value.activeSignature >= 0 &&
"Unexpected negative value for number of active signatures.");
assert(value.activeParameter >= 0 &&
"Unexpected negative value for active parameter index");
return llvm::json::Object{
{"activeSignature", value.activeSignature},
{"activeParameter", value.activeParameter},
{"signatures", llvm::json::Array(value.signatures)},
};
}

View File

@ -871,6 +871,65 @@ struct CompletionParams : TextDocumentPositionParams {
bool fromJSON(const llvm::json::Value &value, CompletionParams &result,
llvm::json::Path path);
//===----------------------------------------------------------------------===//
// ParameterInformation
//===----------------------------------------------------------------------===//
/// A single parameter of a particular signature.
struct ParameterInformation {
/// The label of this parameter. Ignored when labelOffsets is set.
std::string labelString;
/// Inclusive start and exclusive end offsets withing the containing signature
/// label.
Optional<std::pair<unsigned, unsigned>> labelOffsets;
/// The documentation of this parameter. Optional.
std::string documentation;
};
/// Add support for JSON serialization.
llvm::json::Value toJSON(const ParameterInformation &value);
//===----------------------------------------------------------------------===//
// SignatureInformation
//===----------------------------------------------------------------------===//
/// Represents the signature of something callable.
struct SignatureInformation {
/// The label of this signature. Mandatory.
std::string label;
/// The documentation of this signature. Optional.
std::string documentation;
/// The parameters of this signature.
std::vector<ParameterInformation> parameters;
};
/// Add support for JSON serialization.
llvm::json::Value toJSON(const SignatureInformation &value);
raw_ostream &operator<<(raw_ostream &os, const SignatureInformation &value);
//===----------------------------------------------------------------------===//
// SignatureHelp
//===----------------------------------------------------------------------===//
/// Represents the signature of a callable.
struct SignatureHelp {
/// The resulting signatures.
std::vector<SignatureInformation> signatures;
/// The active signature.
int activeSignature = 0;
/// The active parameter of the active signature.
int activeParameter = 0;
};
/// Add support for JSON serialization.
llvm::json::Value toJSON(const SignatureHelp &value);
} // namespace lsp
} // namespace mlir

View File

@ -70,6 +70,12 @@ struct LSPServer {
void onCompletion(const CompletionParams &params,
Callback<CompletionList> reply);
//===--------------------------------------------------------------------===//
// Signature Help
void onSignatureHelp(const TextDocumentPositionParams &params,
Callback<SignatureHelp> reply);
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -109,6 +115,10 @@ void LSPServer::onInitialize(const InitializeParams &params,
{"resolveProvider", false},
{"triggerCharacters", {".", ">", "(", "{", ",", "<", ":", "[", " "}},
}},
{"signatureHelpProvider",
llvm::json::Object{
{"triggerCharacters", {"(", ","}},
}},
{"definitionProvider", true},
{"referencesProvider", true},
{"hoverProvider", true},
@ -209,6 +219,14 @@ void LSPServer::onCompletion(const CompletionParams &params,
reply(server.getCodeCompletion(params.textDocument.uri, params.position));
}
//===----------------------------------------------------------------------===//
// Signature Help
void LSPServer::onSignatureHelp(const TextDocumentPositionParams &params,
Callback<SignatureHelp> reply) {
reply(server.getSignatureHelp(params.textDocument.uri, params.position));
}
//===----------------------------------------------------------------------===//
// Entry Point
//===----------------------------------------------------------------------===//
@ -249,6 +267,10 @@ LogicalResult mlir::lsp::runPdllLSPServer(PDLLServer &server,
messageHandler.method("textDocument/completion", &lspServer,
&LSPServer::onCompletion);
// Signature Help
messageHandler.method("textDocument/signatureHelp", &lspServer,
&LSPServer::onSignatureHelp);
// Diagnostics
lspServer.publishDiagnostics =
messageHandler.outgoingNotification<PublishDiagnosticsParams>(

View File

@ -285,6 +285,13 @@ struct PDLDocument {
lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
const lsp::Position &completePos);
//===--------------------------------------------------------------------===//
// Signature Help
//===--------------------------------------------------------------------===//
lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri,
const lsp::Position &helpPos);
//===--------------------------------------------------------------------===//
// Fields
//===--------------------------------------------------------------------===//
@ -827,6 +834,154 @@ PDLDocument::getCodeCompletion(const lsp::URIForFile &uri,
return completionList;
}
//===----------------------------------------------------------------------===//
// PDLDocument: Signature Help
//===----------------------------------------------------------------------===//
namespace {
class LSPSignatureHelpContext : public CodeCompleteContext {
public:
LSPSignatureHelpContext(SMLoc completeLoc, lsp::SignatureHelp &signatureHelp,
ods::Context &odsContext)
: CodeCompleteContext(completeLoc), signatureHelp(signatureHelp),
odsContext(odsContext) {}
void codeCompleteCallSignature(const ast::CallableDecl *callable,
unsigned currentNumArgs) final {
signatureHelp.activeParameter = currentNumArgs;
lsp::SignatureInformation signatureInfo;
{
llvm::raw_string_ostream strOS(signatureInfo.label);
strOS << callable->getName()->getName() << "(";
auto formatParamFn = [&](const ast::VariableDecl *var) {
unsigned paramStart = strOS.str().size();
strOS << var->getName().getName() << ": " << var->getType();
unsigned paramEnd = strOS.str().size();
signatureInfo.parameters.emplace_back(lsp::ParameterInformation{
StringRef(strOS.str()).slice(paramStart, paramEnd).str(),
std::make_pair(paramStart, paramEnd), /*paramDoc*/ std::string()});
};
llvm::interleaveComma(callable->getInputs(), strOS, formatParamFn);
strOS << ") -> " << callable->getResultType();
}
signatureHelp.signatures.emplace_back(std::move(signatureInfo));
}
void
codeCompleteOperationOperandsSignature(Optional<StringRef> opName,
unsigned currentNumOperands) final {
const ods::Operation *odsOp =
opName ? odsContext.lookupOperation(*opName) : nullptr;
codeCompleteOperationOperandOrResultSignature(
opName, odsOp, odsOp ? odsOp->getOperands() : llvm::None,
currentNumOperands, "operand", "Value");
}
void codeCompleteOperationResultsSignature(Optional<StringRef> opName,
unsigned currentNumResults) final {
const ods::Operation *odsOp =
opName ? odsContext.lookupOperation(*opName) : nullptr;
codeCompleteOperationOperandOrResultSignature(
opName, odsOp, odsOp ? odsOp->getResults() : llvm::None,
currentNumResults, "result", "Type");
}
void codeCompleteOperationOperandOrResultSignature(
Optional<StringRef> opName, const ods::Operation *odsOp,
ArrayRef<ods::OperandOrResult> values, unsigned currentValue,
StringRef label, StringRef dataType) {
signatureHelp.activeParameter = currentValue;
// If we have ODS information for the operation, add in the ODS signature
// for the operation. We also verify that the current number of values is
// not more than what is defined in ODS, as this will result in an error
// anyways.
if (odsOp && currentValue < values.size()) {
lsp::SignatureInformation signatureInfo;
// Build the signature label.
{
llvm::raw_string_ostream strOS(signatureInfo.label);
strOS << "(";
auto formatFn = [&](const ods::OperandOrResult &value) {
unsigned paramStart = strOS.str().size();
strOS << value.getName() << ": ";
StringRef constraintDoc = value.getConstraint().getSummary();
std::string paramDoc;
switch (value.getVariableLengthKind()) {
case ods::VariableLengthKind::Single:
strOS << dataType;
paramDoc = constraintDoc.str();
break;
case ods::VariableLengthKind::Optional:
strOS << dataType << "?";
paramDoc = ("optional: " + constraintDoc).str();
break;
case ods::VariableLengthKind::Variadic:
strOS << dataType << "Range";
paramDoc = ("variadic: " + constraintDoc).str();
break;
}
unsigned paramEnd = strOS.str().size();
signatureInfo.parameters.emplace_back(lsp::ParameterInformation{
StringRef(strOS.str()).slice(paramStart, paramEnd).str(),
std::make_pair(paramStart, paramEnd), paramDoc});
};
llvm::interleaveComma(values, strOS, formatFn);
strOS << ")";
}
signatureInfo.documentation =
llvm::formatv("`op<{0}>` ODS {1} specification", *opName, label)
.str();
signatureHelp.signatures.emplace_back(std::move(signatureInfo));
}
// If there aren't any arguments yet, we also add the generic signature.
if (currentValue == 0 && (!odsOp || !values.empty())) {
lsp::SignatureInformation signatureInfo;
signatureInfo.label =
llvm::formatv("(<{0}s>: {1}Range)", label, dataType).str();
signatureInfo.documentation =
("Generic operation " + label + " specification").str();
signatureInfo.parameters.emplace_back(lsp::ParameterInformation{
StringRef(signatureInfo.label).drop_front().drop_back().str(),
std::pair<unsigned, unsigned>(1, signatureInfo.label.size() - 1),
("All of the " + label + "s of the operation.").str()});
signatureHelp.signatures.emplace_back(std::move(signatureInfo));
}
}
private:
lsp::SignatureHelp &signatureHelp;
ods::Context &odsContext;
};
} // namespace
lsp::SignatureHelp PDLDocument::getSignatureHelp(const lsp::URIForFile &uri,
const lsp::Position &helpPos) {
SMLoc posLoc = helpPos.getAsSMLoc(sourceMgr);
if (!posLoc.isValid())
return lsp::SignatureHelp();
// Adjust the position one further to after the completion trigger token.
posLoc = SMLoc::getFromPointer(posLoc.getPointer() + 1);
// To perform code completion, we run another parse of the module with the
// code completion context provided.
ods::Context tmpODSContext;
lsp::SignatureHelp signatureHelp;
LSPSignatureHelpContext completeContext(posLoc, signatureHelp, tmpODSContext);
ast::Context tmpContext(tmpODSContext);
(void)parsePDLAST(tmpContext, sourceMgr, &completeContext);
return signatureHelp;
}
//===----------------------------------------------------------------------===//
// PDLTextFileChunk
//===----------------------------------------------------------------------===//
@ -883,6 +1038,8 @@ public:
void findDocumentSymbols(std::vector<lsp::DocumentSymbol> &symbols);
lsp::CompletionList getCodeCompletion(const lsp::URIForFile &uri,
lsp::Position completePos);
lsp::SignatureHelp getSignatureHelp(const lsp::URIForFile &uri,
lsp::Position helpPos);
private:
/// Find the PDL document that contains the given position, and update the
@ -1036,6 +1193,11 @@ lsp::CompletionList PDLTextFile::getCodeCompletion(const lsp::URIForFile &uri,
return completionList;
}
lsp::SignatureHelp PDLTextFile::getSignatureHelp(const lsp::URIForFile &uri,
lsp::Position helpPos) {
return getChunkFor(helpPos).document.getSignatureHelp(uri, helpPos);
}
PDLTextFileChunk &PDLTextFile::getChunkFor(lsp::Position &pos) {
if (chunks.size() == 1)
return *chunks.front();
@ -1123,3 +1285,11 @@ lsp::PDLLServer::getCodeCompletion(const URIForFile &uri,
return fileIt->second->getCodeCompletion(uri, completePos);
return CompletionList();
}
lsp::SignatureHelp lsp::PDLLServer::getSignatureHelp(const URIForFile &uri,
const Position &helpPos) {
auto fileIt = impl->files.find(uri.file());
if (fileIt != impl->files.end())
return fileIt->second->getSignatureHelp(uri, helpPos);
return SignatureHelp();
}

View File

@ -20,6 +20,7 @@ struct DocumentSymbol;
struct Hover;
struct Location;
struct Position;
struct SignatureHelp;
class URIForFile;
/// This class implements all of the PDLL related functionality necessary for a
@ -62,6 +63,10 @@ public:
CompletionList getCodeCompletion(const URIForFile &uri,
const Position &completePos);
/// Get the signature help for the position within the given file.
SignatureHelp getSignatureHelp(const URIForFile &uri,
const Position &helpPos);
private:
struct Impl;

View File

@ -16,6 +16,12 @@
// CHECK-NEXT: "documentSymbolProvider": true,
// CHECK-NEXT: "hoverProvider": true,
// CHECK-NEXT: "referencesProvider": true,
// CHECK-NEXT: "signatureHelpProvider": {
// CHECK-NEXT: "triggerCharacters": [
// CHECK-NEXT: "(",
// CHECK-NEXT: ","
// CHECK-NEXT: ]
// CHECK-NEXT: },
// CHECK-NEXT: "textDocumentSync": {
// CHECK-NEXT: "change": 1,
// CHECK-NEXT: "openClose": true,

View File

@ -0,0 +1,89 @@
// RUN: mlir-pdll-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"pdll","capabilities":{},"trace":"off"}}
// -----
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
"uri":"test:///foo.pdll",
"languageId":"pdll",
"version":1,
"text":"Constraint ValueCst(value: Value);\nPattern {\nlet root = op<test.op>() -> ();\nValueCst(root);\nerase root;\n}"
}}}
// -----
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{
"textDocument":{"uri":"test:///foo.pdll"},
"position":{"line":2,"character":23}
}}
// CHECK: "id": 1
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": {
// CHECK-NEXT: "activeParameter": 0,
// CHECK-NEXT: "activeSignature": 0,
// CHECK-NEXT: "signatures": [
// CHECK-NEXT: {
// CHECK-NEXT: "documentation": "Generic operation operand specification",
// CHECK-NEXT: "label": "(<operands>: ValueRange)",
// CHECK-NEXT: "parameters": [
// CHECK-NEXT: {
// CHECK-NEXT: "documentation": "All of the operands of the operation.",
// CHECK-NEXT: "label": [
// CHECK-NEXT: 1,
// CHECK-NEXT: 23
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// -----
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{
"textDocument":{"uri":"test:///foo.pdll"},
"position":{"line":2,"character":29}
}}
// CHECK: "id": 1
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": {
// CHECK-NEXT: "activeParameter": 0,
// CHECK-NEXT: "activeSignature": 0,
// CHECK-NEXT: "signatures": [
// CHECK-NEXT: {
// CHECK-NEXT: "documentation": "Generic operation result specification",
// CHECK-NEXT: "label": "(<results>: TypeRange)",
// CHECK-NEXT: "parameters": [
// CHECK-NEXT: {
// CHECK-NEXT: "documentation": "All of the results of the operation.",
// CHECK-NEXT: "label": [
// CHECK-NEXT: 1,
// CHECK-NEXT: 21
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// -----
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{
"textDocument":{"uri":"test:///foo.pdll"},
"position":{"line":3,"character":9}
}}
// CHECK: "id": 1
// CHECK-NEXT: "jsonrpc": "2.0",
// CHECK-NEXT: "result": {
// CHECK-NEXT: "activeParameter": 0,
// CHECK-NEXT: "activeSignature": 0,
// CHECK-NEXT: "signatures": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": "ValueCst(value: Value) -> Tuple<>",
// CHECK-NEXT: "parameters": [
// CHECK-NEXT: {
// CHECK-NEXT: "label": [
// CHECK-NEXT: 9,
// CHECK-NEXT: 21
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// -----
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
// -----
{"jsonrpc":"2.0","method":"exit"}