[clangd] New conventions for JSON-marshalling functions, centralize machinery

Summary:
 - JSON<->Obj interface is now ADL functions, so they play nicely with enums
 - recursive vector/map parsing and ObjectMapper moved to JSONExpr and tested
 - renamed (un)parse to (de)serialize, since text -> JSON is called parse
 - Protocol.cpp gets a bit shorter

Sorry for the giant patch, it's prety mechanical though

Reviewers: ilya-biryukov

Subscribers: klimek, cfe-commits

Differential Revision: https://reviews.llvm.org/D40596

llvm-svn: 319478
This commit is contained in:
Sam McCall 2017-11-30 21:32:29 +00:00
parent d76814200b
commit ff8b874548
7 changed files with 382 additions and 450 deletions

View File

@ -117,7 +117,7 @@ void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
// We don't need the response so id == 1 is OK.
// Ideally, we would wait for the response and if there is no error, we
// would reply success/failure to the original RPC.
C.call("workspace/applyEdit", ApplyWorkspaceEditParams::unparse(ApplyEdit));
C.call("workspace/applyEdit", ApplyEdit);
} else {
// We should not get here because ExecuteCommandParams would not have
// parsed in the first place and this handler should not be called. But if
@ -140,7 +140,7 @@ void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) {
std::vector<TextEdit> Edits = replacementsToEdits(Code, *Replacements);
WorkspaceEdit WE;
WE.changes = {{Params.textDocument.uri.uri, Edits}};
C.reply(WorkspaceEdit::unparse(WE));
C.reply(WE);
}
void ClangdLSPServer::onDocumentDidClose(Ctx C,

View File

@ -36,7 +36,7 @@ namespace json {
// - booleans
// - null: nullptr
// - arrays: {"foo", 42.0, false}
// - serializable things: any T with a T::unparse(const T&) -> Expr
// - serializable things: types with toJSON(const T&)->Expr, found by ADL
//
// They can also be constructed from object/array helpers:
// - json::obj is a type like map<StringExpr, Expr>
@ -65,6 +65,28 @@ namespace json {
// if (Optional<StringRef> Font = Opts->getString("font"))
// assert(Opts->at("font").kind() == Expr::String);
//
// === Converting expressions to objects ===
//
// The convention is to have a deserializer function findable via ADL:
// fromJSON(const json::Expr&, T&)->bool
// Deserializers are provided for:
// - bool
// - int
// - double
// - std::string
// - vector<T>, where T is deserializable
// - map<string, T>, where T is deserializable
// - Optional<T>, where T is deserializable
//
// ObjectMapper can help writing fromJSON() functions for object types:
// bool fromJSON(const Expr &E, MyStruct &R) {
// ObjectMapper O(E);
// if (!O || !O.map("mandatory_field", R.MandatoryField))
// return false;
// O.map("optional_field", R.OptionalField);
// return true;
// }
//
// === Serialization ===
//
// Exprs can be serialized to JSON:
@ -127,12 +149,11 @@ public:
Expr(T D) : Type(T_Number) {
create<double>(D);
}
// Types with a static T::unparse function returning an Expr.
// FIXME: should this be a free unparse() function found by ADL?
// Types with a toJSON(const T&)->Expr function, found by ADL.
template <typename T,
typename = typename std::enable_if<std::is_same<
Expr, decltype(T::unparse(*(const T *)nullptr))>::value>>
Expr(const T &V) : Expr(T::unparse(V)) {}
Expr, decltype(toJSON(*(const T *)nullptr))>::value>>
Expr(const T &V) : Expr(toJSON(V)) {}
Expr &operator=(const Expr &M) {
destroy();
@ -432,6 +453,101 @@ inline Expr::ObjectExpr::ObjectExpr(std::initializer_list<KV> Properties) {
using obj = Expr::ObjectExpr;
using ary = Expr::ArrayExpr;
// Standard deserializers.
inline bool fromJSON(const json::Expr &E, std::string &Out) {
if (auto S = E.asString()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const json::Expr &E, int &Out) {
if (auto S = E.asInteger()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const json::Expr &E, double &Out) {
if (auto S = E.asNumber()) {
Out = *S;
return true;
}
return false;
}
inline bool fromJSON(const json::Expr &E, bool &Out) {
if (auto S = E.asBoolean()) {
Out = *S;
return true;
}
return false;
}
template <typename T>
bool fromJSON(const json::Expr &E, llvm::Optional<T> &Out) {
if (E.asNull()) {
Out = llvm::None;
return true;
}
T Result;
if (!fromJSON(E, Result))
return false;
Out = std::move(Result);
return true;
}
template <typename T> bool fromJSON(const json::Expr &E, std::vector<T> &Out) {
if (auto *A = E.asArray()) {
Out.clear();
Out.resize(A->size());
for (size_t I = 0; I < A->size(); ++I)
if (!fromJSON((*A)[I], Out[I]))
return false;
return true;
}
return false;
}
template <typename T>
bool fromJSON(const json::Expr &E, std::map<std::string, T> &Out) {
if (auto *O = E.asObject()) {
Out.clear();
for (const auto &KV : *O)
if (!fromJSON(KV.second, Out[llvm::StringRef(KV.first)]))
return false;
return true;
}
return false;
}
// Helper for mapping JSON objects onto protocol structs.
// See file header for example.
class ObjectMapper {
public:
ObjectMapper(const json::Expr &E) : O(E.asObject()) {}
// True if the expression is an object.
// Must be checked before calling map().
operator bool() { return O; }
// Maps a property to a field, if it exists.
template <typename T> bool map(const char *Prop, T &Out) {
assert(*this && "Must check this is an object before calling map()");
if (const json::Expr *E = O->get(Prop))
return fromJSON(*E, Out);
return false;
}
// Optional requires special handling, because missing keys are OK.
template <typename T> bool map(const char *Prop, llvm::Optional<T> &Out) {
assert(*this && "Must check this is an object before calling map()");
if (const json::Expr *E = O->get(Prop))
return fromJSON(*E, Out);
Out = llvm::None;
return true;
}
private:
const json::obj *O;
};
llvm::Expected<Expr> parse(llvm::StringRef JSON);
class ParseError : public llvm::ErrorInfo<ParseError> {

View File

@ -8,7 +8,6 @@
//===----------------------------------------------------------------------===//
//
// This file contains the serialization code for the LSP structs.
// FIXME: This is extremely repetetive and ugly. Is there a better way?
//
//===----------------------------------------------------------------------===//
@ -21,151 +20,8 @@
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace clang::clangd;
namespace {
// Helper for mapping JSON objects onto our protocol structs. Intended use:
// Optional<Result> parse(json::Expr E) {
// ObjectParser O(E);
// if (!O || !O.parse("mandatory_field", Result.MandatoryField))
// return None;
// O.parse("optional_field", Result.OptionalField);
// return Result;
// }
// FIXME: the static methods here should probably become the public parse()
// extension point. Overloading free functions allows us to uniformly handle
// enums, vectors, etc.
class ObjectParser {
public:
ObjectParser(const json::Expr &E) : O(E.asObject()) {}
// True if the expression is an object.
operator bool() { return O; }
template <typename T> bool parse(const char *Prop, T &Out) {
assert(*this && "Must check this is an object before calling parse()");
if (const json::Expr *E = O->get(Prop))
return parse(*E, Out);
return false;
}
// Optional requires special handling, because missing keys are OK.
template <typename T> bool parse(const char *Prop, llvm::Optional<T> &Out) {
assert(*this && "Must check this is an object before calling parse()");
if (const json::Expr *E = O->get(Prop))
return parse(*E, Out);
Out = None;
return true;
}
private:
// Primitives.
static bool parse(const json::Expr &E, std::string &Out) {
if (auto S = E.asString()) {
Out = *S;
return true;
}
return false;
}
static bool parse(const json::Expr &E, int &Out) {
if (auto S = E.asInteger()) {
Out = *S;
return true;
}
return false;
}
static bool parse(const json::Expr &E, bool &Out) {
if (auto S = E.asBoolean()) {
Out = *S;
return true;
}
return false;
}
// Types with a parse() function.
template <typename T> static bool parse(const json::Expr &E, T &Out) {
if (auto Parsed = std::remove_reference<T>::type::parse(E)) {
Out = std::move(*Parsed);
return true;
}
return false;
}
// Nullable values as Optional<T>.
template <typename T>
static bool parse(const json::Expr &E, llvm::Optional<T> &Out) {
if (E.asNull()) {
Out = None;
return true;
}
T Result;
if (!parse(E, Result))
return false;
Out = std::move(Result);
return true;
}
// Array values with std::vector type.
template <typename T>
static bool parse(const json::Expr &E, std::vector<T> &Out) {
if (auto *A = E.asArray()) {
Out.clear();
Out.resize(A->size());
for (size_t I = 0; I < A->size(); ++I)
if (!parse((*A)[I], Out[I]))
return false;
return true;
}
return false;
}
// Object values with std::map<std::string, ?>
template <typename T>
static bool parse(const json::Expr &E, std::map<std::string, T> &Out) {
if (auto *O = E.asObject()) {
for (const auto &KV : *O)
if (!parse(KV.second, Out[StringRef(KV.first)]))
return false;
return true;
}
return false;
}
// Special cased enums, which can't have T::parse() functions.
// FIXME: make everything free functions so there's no special casing.
static bool parse(const json::Expr &E, TraceLevel &Out) {
if (auto S = E.asString()) {
if (*S == "off") {
Out = TraceLevel::Off;
return true;
} else if (*S == "messages") {
Out = TraceLevel::Messages;
return true;
} else if (*S == "verbose") {
Out = TraceLevel::Verbose;
return true;
}
}
return false;
}
static bool parse(const json::Expr &E, FileChangeType &Out) {
if (auto T = E.asInteger()) {
if (*T < static_cast<int>(FileChangeType::Created) ||
*T > static_cast<int>(FileChangeType::Deleted))
return false;
Out = static_cast<FileChangeType>(*T);
return true;
}
return false;
}
const json::obj *O;
};
} // namespace
namespace clang {
namespace clangd {
URI URI::fromUri(llvm::StringRef uri) {
URI Result;
@ -194,276 +50,224 @@ URI URI::fromFile(llvm::StringRef file) {
return Result;
}
llvm::Optional<URI> URI::parse(const json::Expr &E) {
if (auto S = E.asString())
return fromUri(*S);
return None;
bool fromJSON(const json::Expr &E, URI &R) {
if (auto S = E.asString()) {
R = URI::fromUri(*S);
return true;
}
return false;
}
json::Expr URI::unparse(const URI &U) { return U.uri; }
json::Expr toJSON(const URI &U) { return U.uri; }
llvm::Optional<TextDocumentIdentifier>
TextDocumentIdentifier::parse(const json::Expr &Params) {
ObjectParser O(Params);
TextDocumentIdentifier R;
if (!O || !O.parse("uri", R.uri))
return None;
return R;
bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri);
}
llvm::Optional<Position> Position::parse(const json::Expr &Params) {
ObjectParser O(Params);
Position R;
if (!O || !O.parse("line", R.line) || !O.parse("character", R.character))
return None;
return R;
bool fromJSON(const json::Expr &Params, Position &R) {
json::ObjectMapper O(Params);
return O && O.map("line", R.line) && O.map("character", R.character);
}
json::Expr Position::unparse(const Position &P) {
json::Expr toJSON(const Position &P) {
return json::obj{
{"line", P.line},
{"character", P.character},
};
}
llvm::Optional<Range> Range::parse(const json::Expr &Params) {
ObjectParser O(Params);
Range R;
if (!O || !O.parse("start", R.start) || !O.parse("end", R.end))
return None;
return R;
bool fromJSON(const json::Expr &Params, Range &R) {
json::ObjectMapper O(Params);
return O && O.map("start", R.start) && O.map("end", R.end);
}
json::Expr Range::unparse(const Range &P) {
json::Expr toJSON(const Range &P) {
return json::obj{
{"start", P.start},
{"end", P.end},
};
}
json::Expr Location::unparse(const Location &P) {
json::Expr toJSON(const Location &P) {
return json::obj{
{"uri", P.uri},
{"range", P.range},
};
}
llvm::Optional<TextDocumentItem>
TextDocumentItem::parse(const json::Expr &Params) {
ObjectParser O(Params);
TextDocumentItem R;
if (!O || !O.parse("uri", R.uri) || !O.parse("languageId", R.languageId) ||
!O.parse("version", R.version) || !O.parse("text", R.text))
return None;
return R;
bool fromJSON(const json::Expr &Params, TextDocumentItem &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) &&
O.map("version", R.version) && O.map("text", R.text);
}
llvm::Optional<Metadata> Metadata::parse(const json::Expr &Params) {
ObjectParser O(Params);
Metadata R;
bool fromJSON(const json::Expr &Params, Metadata &R) {
json::ObjectMapper O(Params);
if (!O)
return None;
O.parse("extraFlags", R.extraFlags);
return R;
return false;
O.map("extraFlags", R.extraFlags);
return true;
}
llvm::Optional<TextEdit> TextEdit::parse(const json::Expr &Params) {
ObjectParser O(Params);
TextEdit R;
if (!O || !O.parse("range", R.range) || !O.parse("newText", R.newText))
return None;
return R;
bool fromJSON(const json::Expr &Params, TextEdit &R) {
json::ObjectMapper O(Params);
return O && O.map("range", R.range) && O.map("newText", R.newText);
}
json::Expr TextEdit::unparse(const TextEdit &P) {
json::Expr toJSON(const TextEdit &P) {
return json::obj{
{"range", P.range},
{"newText", P.newText},
};
}
llvm::Optional<InitializeParams>
InitializeParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
InitializeParams R;
bool fromJSON(const json::Expr &E, TraceLevel &Out) {
if (auto S = E.asString()) {
if (*S == "off") {
Out = TraceLevel::Off;
return true;
} else if (*S == "messages") {
Out = TraceLevel::Messages;
return true;
} else if (*S == "verbose") {
Out = TraceLevel::Verbose;
return true;
}
}
return false;
}
bool fromJSON(const json::Expr &Params, InitializeParams &R) {
json::ObjectMapper O(Params);
if (!O)
return None;
return false;
// We deliberately don't fail if we can't parse individual fields.
// Failing to handle a slightly malformed initialize would be a disaster.
O.parse("processId", R.processId);
O.parse("rootUri", R.rootUri);
O.parse("rootPath", R.rootPath);
O.parse("trace", R.trace);
O.map("processId", R.processId);
O.map("rootUri", R.rootUri);
O.map("rootPath", R.rootPath);
O.map("trace", R.trace);
// initializationOptions, capabilities unused
return R;
return true;
}
llvm::Optional<DidOpenTextDocumentParams>
DidOpenTextDocumentParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DidOpenTextDocumentParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("metadata", R.metadata))
return None;
return R;
bool fromJSON(const json::Expr &Params, DidOpenTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("metadata", R.metadata);
}
llvm::Optional<DidCloseTextDocumentParams>
DidCloseTextDocumentParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DidCloseTextDocumentParams R;
if (!O || !O.parse("textDocument", R.textDocument))
return None;
return R;
bool fromJSON(const json::Expr &Params, DidCloseTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument);
}
llvm::Optional<DidChangeTextDocumentParams>
DidChangeTextDocumentParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DidChangeTextDocumentParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("contentChanges", R.contentChanges))
return None;
return R;
bool fromJSON(const json::Expr &Params, DidChangeTextDocumentParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("contentChanges", R.contentChanges);
}
llvm::Optional<FileEvent> FileEvent::parse(const json::Expr &Params) {
ObjectParser O(Params);
FileEvent R;
if (!O || !O.parse("uri", R.uri) || !O.parse("type", R.type))
return None;
return R;
bool fromJSON(const json::Expr &E, FileChangeType &Out) {
if (auto T = E.asInteger()) {
if (*T < static_cast<int>(FileChangeType::Created) ||
*T > static_cast<int>(FileChangeType::Deleted))
return false;
Out = static_cast<FileChangeType>(*T);
return true;
}
return false;
}
llvm::Optional<DidChangeWatchedFilesParams>
DidChangeWatchedFilesParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DidChangeWatchedFilesParams R;
if (!O || !O.parse("changes", R.changes))
return None;
return R;
bool fromJSON(const json::Expr &Params, FileEvent &R) {
json::ObjectMapper O(Params);
return O && O.map("uri", R.uri) && O.map("type", R.type);
}
llvm::Optional<TextDocumentContentChangeEvent>
TextDocumentContentChangeEvent::parse(const json::Expr &Params) {
ObjectParser O(Params);
TextDocumentContentChangeEvent R;
if (!O || !O.parse("text", R.text))
return None;
return R;
bool fromJSON(const json::Expr &Params, DidChangeWatchedFilesParams &R) {
json::ObjectMapper O(Params);
return O && O.map("changes", R.changes);
}
llvm::Optional<FormattingOptions>
FormattingOptions::parse(const json::Expr &Params) {
ObjectParser O(Params);
FormattingOptions R;
if (!O || !O.parse("tabSize", R.tabSize) ||
!O.parse("insertSpaces", R.insertSpaces))
return None;
return R;
bool fromJSON(const json::Expr &Params, TextDocumentContentChangeEvent &R) {
json::ObjectMapper O(Params);
return O && O.map("text", R.text);
}
json::Expr FormattingOptions::unparse(const FormattingOptions &P) {
bool fromJSON(const json::Expr &Params, FormattingOptions &R) {
json::ObjectMapper O(Params);
return O && O.map("tabSize", R.tabSize) &&
O.map("insertSpaces", R.insertSpaces);
}
json::Expr toJSON(const FormattingOptions &P) {
return json::obj{
{"tabSize", P.tabSize},
{"insertSpaces", P.insertSpaces},
};
}
llvm::Optional<DocumentRangeFormattingParams>
DocumentRangeFormattingParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DocumentRangeFormattingParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("range", R.range) || !O.parse("options", R.options))
return None;
return R;
bool fromJSON(const json::Expr &Params, DocumentRangeFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("range", R.range) && O.map("options", R.options);
}
llvm::Optional<DocumentOnTypeFormattingParams>
DocumentOnTypeFormattingParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DocumentOnTypeFormattingParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("position", R.position) || !O.parse("ch", R.ch) ||
!O.parse("options", R.options))
return None;
return R;
bool fromJSON(const json::Expr &Params, DocumentOnTypeFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("ch", R.ch) &&
O.map("options", R.options);
}
llvm::Optional<DocumentFormattingParams>
DocumentFormattingParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
DocumentFormattingParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("options", R.options))
return None;
return R;
bool fromJSON(const json::Expr &Params, DocumentFormattingParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("options", R.options);
}
llvm::Optional<Diagnostic> Diagnostic::parse(const json::Expr &Params) {
ObjectParser O(Params);
Diagnostic R;
if (!O || !O.parse("range", R.range) || !O.parse("message", R.message))
return None;
O.parse("severity", R.severity);
return R;
bool fromJSON(const json::Expr &Params, Diagnostic &R) {
json::ObjectMapper O(Params);
if (!O || !O.map("range", R.range) || !O.map("message", R.message))
return false;
O.map("severity", R.severity);
return true;
}
llvm::Optional<CodeActionContext>
CodeActionContext::parse(const json::Expr &Params) {
ObjectParser O(Params);
CodeActionContext R;
if (!O || !O.parse("diagnostics", R.diagnostics))
return None;
return R;
bool fromJSON(const json::Expr &Params, CodeActionContext &R) {
json::ObjectMapper O(Params);
return O && O.map("diagnostics", R.diagnostics);
}
llvm::Optional<CodeActionParams>
CodeActionParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
CodeActionParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("range", R.range) || !O.parse("context", R.context))
return None;
return R;
bool fromJSON(const json::Expr &Params, CodeActionParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("range", R.range) && O.map("context", R.context);
}
llvm::Optional<WorkspaceEdit> WorkspaceEdit::parse(const json::Expr &Params) {
ObjectParser O(Params);
WorkspaceEdit R;
if (!O || !O.parse("changes", R.changes))
return None;
return R;
bool fromJSON(const json::Expr &Params, WorkspaceEdit &R) {
json::ObjectMapper O(Params);
return O && O.map("changes", R.changes);
}
const std::string ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
"clangd.applyFix";
llvm::Optional<ExecuteCommandParams>
ExecuteCommandParams::parse(const json::Expr &Params) {
const json::obj *O = Params.asObject();
if (!O)
return None;
bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) {
json::ObjectMapper O(Params);
if (!O || !O.map("command", R.command))
return false;
ExecuteCommandParams Result;
if (auto Command = O->getString("command"))
Result.command = *Command;
auto Args = O->getArray("arguments");
if (Result.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
if (!Args || Args->size() != 1)
return llvm::None;
if (auto Parsed = WorkspaceEdit::parse(Args->front()))
Result.workspaceEdit = std::move(*Parsed);
else
return llvm::None;
} else
return llvm::None; // Unrecognized command.
return Result;
auto Args = Params.asObject()->getArray("arguments");
if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
return Args && Args->size() == 1 &&
fromJSON(Args->front(), R.workspaceEdit);
}
return false; // Unrecognized command.
}
json::Expr WorkspaceEdit::unparse(const WorkspaceEdit &WE) {
json::Expr toJSON(const WorkspaceEdit &WE) {
if (!WE.changes)
return json::obj{};
json::obj FileChanges;
@ -472,22 +276,17 @@ json::Expr WorkspaceEdit::unparse(const WorkspaceEdit &WE) {
return json::obj{{"changes", std::move(FileChanges)}};
}
json::Expr
ApplyWorkspaceEditParams::unparse(const ApplyWorkspaceEditParams &Params) {
json::Expr toJSON(const ApplyWorkspaceEditParams &Params) {
return json::obj{{"edit", Params.edit}};
}
llvm::Optional<TextDocumentPositionParams>
TextDocumentPositionParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
TextDocumentPositionParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("position", R.position))
return None;
return R;
bool fromJSON(const json::Expr &Params, TextDocumentPositionParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position);
}
json::Expr CompletionItem::unparse(const CompletionItem &CI) {
json::Expr toJSON(const CompletionItem &CI) {
assert(!CI.label.empty() && "completion item label is required");
json::obj Result{{"label", CI.label}};
if (CI.kind != CompletionItemKind::Missing)
@ -511,19 +310,19 @@ json::Expr CompletionItem::unparse(const CompletionItem &CI) {
return std::move(Result);
}
bool clangd::operator<(const CompletionItem &L, const CompletionItem &R) {
bool operator<(const CompletionItem &L, const CompletionItem &R) {
return (L.sortText.empty() ? L.label : L.sortText) <
(R.sortText.empty() ? R.label : R.sortText);
}
json::Expr CompletionList::unparse(const CompletionList &L) {
json::Expr toJSON(const CompletionList &L) {
return json::obj{
{"isIncomplete", L.isIncomplete},
{"items", json::ary(L.items)},
};
}
json::Expr ParameterInformation::unparse(const ParameterInformation &PI) {
json::Expr toJSON(const ParameterInformation &PI) {
assert(!PI.label.empty() && "parameter information label is required");
json::obj Result{{"label", PI.label}};
if (!PI.documentation.empty())
@ -531,7 +330,7 @@ json::Expr ParameterInformation::unparse(const ParameterInformation &PI) {
return std::move(Result);
}
json::Expr SignatureInformation::unparse(const SignatureInformation &SI) {
json::Expr toJSON(const SignatureInformation &SI) {
assert(!SI.label.empty() && "signature information label is required");
json::obj Result{
{"label", SI.label},
@ -542,7 +341,7 @@ json::Expr SignatureInformation::unparse(const SignatureInformation &SI) {
return std::move(Result);
}
json::Expr SignatureHelp::unparse(const SignatureHelp &SH) {
json::Expr toJSON(const SignatureHelp &SH) {
assert(SH.activeSignature >= 0 &&
"Unexpected negative value for number of active signatures.");
assert(SH.activeParameter >= 0 &&
@ -554,11 +353,11 @@ json::Expr SignatureHelp::unparse(const SignatureHelp &SH) {
};
}
llvm::Optional<RenameParams> RenameParams::parse(const json::Expr &Params) {
ObjectParser O(Params);
RenameParams R;
if (!O || !O.parse("textDocument", R.textDocument) ||
!O.parse("position", R.position) || !O.parse("newName", R.newName))
return None;
return R;
bool fromJSON(const json::Expr &Params, RenameParams &R) {
json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("newName", R.newName);
}
} // namespace clangd
} // namespace clang

View File

@ -13,8 +13,8 @@
// This is not meant to be a complete implementation, new interfaces are added
// when they're needed.
//
// Each struct has a parse and unparse function, that converts back and forth
// between the struct and a JSON representation.
// Each struct has a toJSON and fromJSON function, that converts between
// the struct and a JSON representation. (See JSONExpr.h)
//
//===----------------------------------------------------------------------===//
@ -51,9 +51,6 @@ struct URI {
static URI fromUri(llvm::StringRef uri);
static URI fromFile(llvm::StringRef file);
static llvm::Optional<URI> parse(const json::Expr &U);
static json::Expr unparse(const URI &U);
friend bool operator==(const URI &LHS, const URI &RHS) {
return LHS.uri == RHS.uri;
}
@ -66,13 +63,14 @@ struct URI {
return LHS.uri < RHS.uri;
}
};
json::Expr toJSON(const URI &U);
bool fromJSON(const json::Expr &, URI &);
struct TextDocumentIdentifier {
/// The text document's URI.
URI uri;
static llvm::Optional<TextDocumentIdentifier> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, TextDocumentIdentifier &);
struct Position {
/// Line position in a document (zero-based).
@ -89,10 +87,9 @@ struct Position {
return std::tie(LHS.line, LHS.character) <
std::tie(RHS.line, RHS.character);
}
static llvm::Optional<Position> parse(const json::Expr &Params);
static json::Expr unparse(const Position &P);
};
bool fromJSON(const json::Expr &, Position &);
json::Expr toJSON(const Position &);
struct Range {
/// The range's start position.
@ -107,10 +104,9 @@ struct Range {
friend bool operator<(const Range &LHS, const Range &RHS) {
return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end);
}
static llvm::Optional<Range> parse(const json::Expr &Params);
static json::Expr unparse(const Range &P);
};
bool fromJSON(const json::Expr &, Range &);
json::Expr toJSON(const Range &);
struct Location {
/// The text document's URI.
@ -128,15 +124,13 @@ struct Location {
friend bool operator<(const Location &LHS, const Location &RHS) {
return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range);
}
static json::Expr unparse(const Location &P);
};
json::Expr toJSON(const Location &);
struct Metadata {
std::vector<std::string> extraFlags;
static llvm::Optional<Metadata> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, Metadata &);
struct TextEdit {
/// The range of the text document to be manipulated. To insert
@ -146,10 +140,9 @@ struct TextEdit {
/// The string to be inserted. For delete operations use an
/// empty string.
std::string newText;
static llvm::Optional<TextEdit> parse(const json::Expr &Params);
static json::Expr unparse(const TextEdit &P);
};
bool fromJSON(const json::Expr &, TextEdit &);
json::Expr toJSON(const TextEdit &);
struct TextDocumentItem {
/// The text document's URI.
@ -163,21 +156,18 @@ struct TextDocumentItem {
/// The content of the opened text document.
std::string text;
static llvm::Optional<TextDocumentItem> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, TextDocumentItem &);
enum class TraceLevel {
Off = 0,
Messages = 1,
Verbose = 2,
};
bool fromJSON(const json::Expr &E, TraceLevel &Out);
struct NoParams {
static llvm::Optional<NoParams> parse(const json::Expr &Params) {
return NoParams{};
}
};
struct NoParams {};
inline bool fromJSON(const json::Expr &, NoParams &) { return true; }
using ShutdownParams = NoParams;
using ExitParams = NoParams;
@ -208,8 +198,8 @@ struct InitializeParams {
/// The initial trace setting. If omitted trace is disabled ('off').
llvm::Optional<TraceLevel> trace;
static llvm::Optional<InitializeParams> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, InitializeParams &);
struct DidOpenTextDocumentParams {
/// The document that was opened.
@ -217,26 +207,20 @@ struct DidOpenTextDocumentParams {
/// Extension storing per-file metadata, such as compilation flags.
llvm::Optional<Metadata> metadata;
static llvm::Optional<DidOpenTextDocumentParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DidOpenTextDocumentParams &);
struct DidCloseTextDocumentParams {
/// The document that was closed.
TextDocumentIdentifier textDocument;
static llvm::Optional<DidCloseTextDocumentParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DidCloseTextDocumentParams &);
struct TextDocumentContentChangeEvent {
/// The new text of the document.
std::string text;
static llvm::Optional<TextDocumentContentChangeEvent>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, TextDocumentContentChangeEvent &);
struct DidChangeTextDocumentParams {
/// The document that did change. The version number points
@ -246,10 +230,8 @@ struct DidChangeTextDocumentParams {
/// The actual content changes.
std::vector<TextDocumentContentChangeEvent> contentChanges;
static llvm::Optional<DidChangeTextDocumentParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DidChangeTextDocumentParams &);
enum class FileChangeType {
/// The file got created.
@ -259,23 +241,21 @@ enum class FileChangeType {
/// The file got deleted.
Deleted = 3
};
bool fromJSON(const json::Expr &E, FileChangeType &Out);
struct FileEvent {
/// The file's URI.
URI uri;
/// The change type.
FileChangeType type;
static llvm::Optional<FileEvent> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, FileEvent &);
struct DidChangeWatchedFilesParams {
/// The actual file events.
std::vector<FileEvent> changes;
static llvm::Optional<DidChangeWatchedFilesParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DidChangeWatchedFilesParams &);
struct FormattingOptions {
/// Size of a tab in spaces.
@ -283,10 +263,9 @@ struct FormattingOptions {
/// Prefer spaces over tabs.
bool insertSpaces;
static llvm::Optional<FormattingOptions> parse(const json::Expr &Params);
static json::Expr unparse(const FormattingOptions &P);
};
bool fromJSON(const json::Expr &, FormattingOptions &);
json::Expr toJSON(const FormattingOptions &);
struct DocumentRangeFormattingParams {
/// The document to format.
@ -297,10 +276,8 @@ struct DocumentRangeFormattingParams {
/// The format options
FormattingOptions options;
static llvm::Optional<DocumentRangeFormattingParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DocumentRangeFormattingParams &);
struct DocumentOnTypeFormattingParams {
/// The document to format.
@ -314,10 +291,8 @@ struct DocumentOnTypeFormattingParams {
/// The format options.
FormattingOptions options;
static llvm::Optional<DocumentOnTypeFormattingParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DocumentOnTypeFormattingParams &);
struct DocumentFormattingParams {
/// The document to format.
@ -325,10 +300,8 @@ struct DocumentFormattingParams {
/// The format options
FormattingOptions options;
static llvm::Optional<DocumentFormattingParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, DocumentFormattingParams &);
struct Diagnostic {
/// The range at which the message applies.
@ -358,16 +331,14 @@ struct Diagnostic {
return std::tie(LHS.range, LHS.severity, LHS.message) <
std::tie(RHS.range, RHS.severity, RHS.message);
}
static llvm::Optional<Diagnostic> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, Diagnostic &);
struct CodeActionContext {
/// An array of diagnostics.
std::vector<Diagnostic> diagnostics;
static llvm::Optional<CodeActionContext> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, CodeActionContext &);
struct CodeActionParams {
/// The document in which the command was invoked.
@ -378,9 +349,8 @@ struct CodeActionParams {
/// Context carrying additional information.
CodeActionContext context;
static llvm::Optional<CodeActionParams> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, CodeActionParams &);
struct WorkspaceEdit {
/// Holds changes to existing resources.
@ -388,10 +358,9 @@ struct WorkspaceEdit {
/// Note: "documentChanges" is not currently used because currently there is
/// no support for versioned edits.
static llvm::Optional<WorkspaceEdit> parse(const json::Expr &Params);
static json::Expr unparse(const WorkspaceEdit &WE);
};
bool fromJSON(const json::Expr &, WorkspaceEdit &);
json::Expr toJSON(const WorkspaceEdit &WE);
/// Exact commands are not specified in the protocol so we define the
/// ones supported by Clangd here. The protocol specifies the command arguments
@ -411,14 +380,13 @@ struct ExecuteCommandParams {
// Arguments
llvm::Optional<WorkspaceEdit> workspaceEdit;
static llvm::Optional<ExecuteCommandParams> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, ExecuteCommandParams &);
struct ApplyWorkspaceEditParams {
WorkspaceEdit edit;
static json::Expr unparse(const ApplyWorkspaceEditParams &Params);
};
json::Expr toJSON(const ApplyWorkspaceEditParams &);
struct TextDocumentPositionParams {
/// The text document.
@ -426,10 +394,8 @@ struct TextDocumentPositionParams {
/// The position inside the text document.
Position position;
static llvm::Optional<TextDocumentPositionParams>
parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, TextDocumentPositionParams &);
/// The kind of a completion entry.
enum class CompletionItemKind {
@ -524,8 +490,8 @@ struct CompletionItem {
//
// data?: any - A data entry field that is preserved on a completion item
// between a completion and a completion resolve request.
static json::Expr unparse(const CompletionItem &P);
};
json::Expr toJSON(const CompletionItem &);
bool operator<(const CompletionItem &, const CompletionItem &);
@ -537,9 +503,8 @@ struct CompletionList {
/// The completion items.
std::vector<CompletionItem> items;
static json::Expr unparse(const CompletionList &);
};
json::Expr toJSON(const CompletionList &);
/// A single parameter of a particular signature.
struct ParameterInformation {
@ -549,9 +514,8 @@ struct ParameterInformation {
/// The documentation of this parameter. Optional.
std::string documentation;
static json::Expr unparse(const ParameterInformation &);
};
json::Expr toJSON(const ParameterInformation &);
/// Represents the signature of something callable.
struct SignatureInformation {
@ -564,9 +528,8 @@ struct SignatureInformation {
/// The parameters of this signature.
std::vector<ParameterInformation> parameters;
static json::Expr unparse(const SignatureInformation &);
};
json::Expr toJSON(const SignatureInformation &);
/// Represents the signature of a callable.
struct SignatureHelp {
@ -579,9 +542,8 @@ struct SignatureHelp {
/// The active parameter of the active signature.
int activeParameter = 0;
static json::Expr unparse(const SignatureHelp &);
};
json::Expr toJSON(const SignatureHelp &);
struct RenameParams {
/// The document that was opened.
@ -592,9 +554,8 @@ struct RenameParams {
/// The new name of the symbol.
std::string newName;
static llvm::Optional<RenameParams> parse(const json::Expr &Params);
};
bool fromJSON(const json::Expr &, RenameParams &);
} // namespace clangd
} // namespace clang

View File

@ -21,7 +21,7 @@ namespace {
// Helper for attaching ProtocolCallbacks methods to a JSONRPCDispatcher.
// Invoke like: Registerer("foo", &ProtocolCallbacks::onFoo)
// onFoo should be: void onFoo(Ctx &C, FooParams &Params)
// FooParams should have a static factory method: parse(const json::Expr&).
// FooParams should have a fromJSON function.
struct HandlerRegisterer {
template <typename Param>
void operator()(StringRef Method,
@ -31,11 +31,9 @@ struct HandlerRegisterer {
auto *Callbacks = this->Callbacks;
Dispatcher.registerHandler(
Method, [=](RequestContext C, const json::Expr &RawParams) {
if (auto P = [&] {
trace::Span Tracer("Parse");
return std::decay<Param>::type::parse(RawParams);
}()) {
(Callbacks->*Handler)(std::move(C), *P);
typename std::remove_reference<Param>::type P;
if (fromJSON(RawParams, P)) {
(Callbacks->*Handler)(std::move(C), P);
} else {
Out->log("Failed to decode " + Method + " request.\n");
}

View File

@ -11,7 +11,7 @@ Content-Length: 152
# CHECK: {"displayTimeUnit":"ns","traceEvents":[
# Start opening the doc.
# CHECK: "name": "textDocument/didOpen"
# CHECK: "ph": "E"
# CHECK: "ph": "B"
# Start building the preamble.
# CHECK: "name": "Preamble"
# CHECK: },

View File

@ -229,6 +229,64 @@ TEST(JSONTest, Inspection) {
}
}
// Sample struct with typical JSON-mapping rules.
struct CustomStruct {
CustomStruct() : B(false) {}
CustomStruct(std::string S, llvm::Optional<int> I, bool B)
: S(S), I(I), B(B) {}
std::string S;
llvm::Optional<int> I;
bool B;
};
inline bool operator==(const CustomStruct &L, const CustomStruct &R) {
return L.S == R.S && L.I == R.I && L.B == R.B;
}
inline std::ostream &operator<<(std::ostream &OS, const CustomStruct &S) {
return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None")
<< ", " << S.B << ")";
}
bool fromJSON(const json::Expr &E, CustomStruct &R) {
ObjectMapper O(E);
if (!O || !O.map("str", R.S) || !O.map("int", R.I))
return false;
O.map("bool", R.B);
return true;
}
TEST(JSONTest, Deserialize) {
std::map<std::string, std::vector<CustomStruct>> R;
CustomStruct ExpectedStruct = {"foo", 42, true};
std::map<std::string, std::vector<CustomStruct>> Expected;
Expr J = obj{{"foo", ary{
obj{
{"str", "foo"},
{"int", 42},
{"bool", true},
{"unknown", "ignored"},
},
obj{{"str", "bar"}},
obj{
{"str", "baz"},
{"bool", "string"}, // OK, deserialize ignores.
},
}}};
Expected["foo"] = {
CustomStruct("foo", 42, true),
CustomStruct("bar", llvm::None, false),
CustomStruct("baz", llvm::None, false),
};
ASSERT_TRUE(fromJSON(J, R));
EXPECT_EQ(R, Expected);
CustomStruct V;
EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V;
EXPECT_FALSE(fromJSON(obj{}, V)) << "Missing required field " << V;
EXPECT_FALSE(fromJSON(obj{{"str", 1}}, V)) << "Wrong type " << V;
// Optional<T> must parse as the correct type if present.
EXPECT_FALSE(fromJSON(obj{{"str", 1}, {"int", "string"}}, V))
<< "Wrong type for Optional<T> " << V;
}
} // namespace
} // namespace json
} // namespace clangd