Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -117,7 +117,7 @@ // 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 @@ std::vector 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, Index: clangd/JSONExpr.h =================================================================== --- clangd/JSONExpr.h +++ clangd/JSONExpr.h @@ -36,7 +36,7 @@ // - 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 @@ -65,6 +65,28 @@ // if (Optional 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, where T is deserializable +// - map, where T is deserializable +// - Optional, 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 @@ Expr(T D) : Type(T_Number) { create(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 ::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 @@ 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 +bool fromJSON(const json::Expr &E, llvm::Optional &Out) { + if (E.asNull()) { + Out = llvm::None; + return true; + } + T Result; + if (!fromJSON(E, Result)) + return false; + Out = std::move(Result); + return true; +} +template bool fromJSON(const json::Expr &E, std::vector &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 +bool fromJSON(const json::Expr &E, std::map &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 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 bool map(const char *Prop, llvm::Optional &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 parse(llvm::StringRef JSON); class ParseError : public llvm::ErrorInfo { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -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 @@ static URI fromUri(llvm::StringRef uri); static URI fromFile(llvm::StringRef file); - static llvm::Optional 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 @@ 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 parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, TextDocumentIdentifier &); struct Position { /// Line position in a document (zero-based). @@ -89,10 +87,9 @@ return std::tie(LHS.line, LHS.character) < std::tie(RHS.line, RHS.character); } - - static llvm::Optional 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 @@ 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 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 @@ 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 extraFlags; - - static llvm::Optional 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 @@ /// The string to be inserted. For delete operations use an /// empty string. std::string newText; - - static llvm::Optional 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 @@ /// The content of the opened text document. std::string text; - - static llvm::Optional 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 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 @@ /// The initial trace setting. If omitted trace is disabled ('off'). llvm::Optional trace; - static llvm::Optional parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, InitializeParams &); struct DidOpenTextDocumentParams { /// The document that was opened. @@ -217,26 +207,20 @@ /// Extension storing per-file metadata, such as compilation flags. llvm::Optional metadata; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DidOpenTextDocumentParams &); struct DidCloseTextDocumentParams { /// The document that was closed. TextDocumentIdentifier textDocument; - - static llvm::Optional - 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 - 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 @@ /// The actual content changes. std::vector contentChanges; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DidChangeTextDocumentParams &); enum class FileChangeType { /// The file got created. @@ -259,23 +241,21 @@ /// 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 parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, FileEvent &); struct DidChangeWatchedFilesParams { /// The actual file events. std::vector changes; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DidChangeWatchedFilesParams &); struct FormattingOptions { /// Size of a tab in spaces. @@ -283,10 +263,9 @@ /// Prefer spaces over tabs. bool insertSpaces; - - static llvm::Optional 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 @@ /// The format options FormattingOptions options; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DocumentRangeFormattingParams &); struct DocumentOnTypeFormattingParams { /// The document to format. @@ -314,10 +291,8 @@ /// The format options. FormattingOptions options; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DocumentOnTypeFormattingParams &); struct DocumentFormattingParams { /// The document to format. @@ -325,10 +300,8 @@ /// The format options FormattingOptions options; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, DocumentFormattingParams &); struct Diagnostic { /// The range at which the message applies. @@ -358,16 +331,14 @@ return std::tie(LHS.range, LHS.severity, LHS.message) < std::tie(RHS.range, RHS.severity, RHS.message); } - - static llvm::Optional parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, Diagnostic &); struct CodeActionContext { /// An array of diagnostics. std::vector diagnostics; - - static llvm::Optional 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 @@ /// Context carrying additional information. CodeActionContext context; - - static llvm::Optional parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, CodeActionParams &); struct WorkspaceEdit { /// Holds changes to existing resources. @@ -388,10 +358,9 @@ /// Note: "documentChanges" is not currently used because currently there is /// no support for versioned edits. - - static llvm::Optional 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 @@ // Arguments llvm::Optional workspaceEdit; - - static llvm::Optional 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 @@ /// The position inside the text document. Position position; - - static llvm::Optional - parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, TextDocumentPositionParams &); /// The kind of a completion entry. enum class CompletionItemKind { @@ -524,8 +490,8 @@ // // 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 @@ /// The completion items. std::vector 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 @@ /// 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 @@ /// The parameters of this signature. std::vector parameters; - - static json::Expr unparse(const SignatureInformation &); }; +json::Expr toJSON(const SignatureInformation &); /// Represents the signature of a callable. struct SignatureHelp { @@ -579,9 +542,8 @@ /// 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 @@ /// The new name of the symbol. std::string newName; - - static llvm::Optional parse(const json::Expr &Params); }; +bool fromJSON(const json::Expr &, RenameParams &); } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -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 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 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 bool parse(const char *Prop, llvm::Optional &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 static bool parse(const json::Expr &E, T &Out) { - if (auto Parsed = std::remove_reference::type::parse(E)) { - Out = std::move(*Parsed); - return true; - } - return false; - } - - // Nullable values as Optional. - template - static bool parse(const json::Expr &E, llvm::Optional &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 - static bool parse(const json::Expr &E, std::vector &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 - template - static bool parse(const json::Expr &E, std::map &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(FileChangeType::Created) || - *T > static_cast(FileChangeType::Deleted)) - return false; - Out = static_cast(*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 @@ return Result; } -llvm::Optional 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::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::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::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::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::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::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::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; +} + +bool fromJSON(const json::Expr &Params, DidOpenTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("metadata", R.metadata); +} + +bool fromJSON(const json::Expr &Params, DidCloseTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + +bool fromJSON(const json::Expr &Params, DidChangeTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("contentChanges", R.contentChanges); +} + +bool fromJSON(const json::Expr &E, FileChangeType &Out) { + if (auto T = E.asInteger()) { + if (*T < static_cast(FileChangeType::Created) || + *T > static_cast(FileChangeType::Deleted)) + return false; + Out = static_cast(*T); + return true; + } + return false; } -llvm::Optional -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; -} - -llvm::Optional -DidCloseTextDocumentParams::parse(const json::Expr &Params) { - ObjectParser O(Params); - DidCloseTextDocumentParams R; - if (!O || !O.parse("textDocument", R.textDocument)) - return None; - return R; -} - -llvm::Optional -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; -} - -llvm::Optional 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; -} - -llvm::Optional -DidChangeWatchedFilesParams::parse(const json::Expr &Params) { - ObjectParser O(Params); - DidChangeWatchedFilesParams R; - if (!O || !O.parse("changes", R.changes)) - return None; - return R; -} - -llvm::Optional -TextDocumentContentChangeEvent::parse(const json::Expr &Params) { - ObjectParser O(Params); - TextDocumentContentChangeEvent R; - if (!O || !O.parse("text", R.text)) - return None; - return R; -} - -llvm::Optional -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, FileEvent &R) { + json::ObjectMapper O(Params); + return O && O.map("uri", R.uri) && O.map("type", R.type); } -json::Expr FormattingOptions::unparse(const FormattingOptions &P) { +bool fromJSON(const json::Expr &Params, DidChangeWatchedFilesParams &R) { + json::ObjectMapper O(Params); + return O && O.map("changes", R.changes); +} + +bool fromJSON(const json::Expr &Params, TextDocumentContentChangeEvent &R) { + json::ObjectMapper O(Params); + return O && O.map("text", R.text); +} + +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::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; -} - -llvm::Optional -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; -} - -llvm::Optional -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; -} - -llvm::Optional 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; -} - -llvm::Optional -CodeActionContext::parse(const json::Expr &Params) { - ObjectParser O(Params); - CodeActionContext R; - if (!O || !O.parse("diagnostics", R.diagnostics)) - return None; - return R; -} - -llvm::Optional -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; -} - -llvm::Optional 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, DocumentRangeFormattingParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("range", R.range) && O.map("options", R.options); +} + +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); +} + +bool fromJSON(const json::Expr &Params, DocumentFormattingParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("options", R.options); +} + +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; +} + +bool fromJSON(const json::Expr &Params, CodeActionContext &R) { + json::ObjectMapper O(Params); + return O && O.map("diagnostics", R.diagnostics); +} + +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); +} + +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::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 @@ 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::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 @@ 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 @@ 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 @@ 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 @@ }; } -llvm::Optional 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 Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -21,7 +21,7 @@ // 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 void operator()(StringRef Method, @@ -31,11 +31,9 @@ auto *Callbacks = this->Callbacks; Dispatcher.registerHandler( Method, [=](RequestContext C, const json::Expr &RawParams) { - if (auto P = [&] { - trace::Span Tracer("Parse"); - return std::decay::type::parse(RawParams); - }()) { - (Callbacks->*Handler)(std::move(C), *P); + typename std::remove_reference::type P; + if (fromJSON(RawParams, P)) { + (Callbacks->*Handler)(std::move(C), P); } else { Out->log("Failed to decode " + Method + " request.\n"); } Index: test/clangd/trace.test =================================================================== --- test/clangd/trace.test +++ test/clangd/trace.test @@ -11,7 +11,7 @@ # 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: }, Index: unittests/clangd/JSONExprTests.cpp =================================================================== --- unittests/clangd/JSONExprTests.cpp +++ unittests/clangd/JSONExprTests.cpp @@ -229,6 +229,64 @@ } } +// Sample struct with typical JSON-mapping rules. +struct CustomStruct { + CustomStruct() : B(false) {} + CustomStruct(std::string S, llvm::Optional I, bool B) + : S(S), I(I), B(B) {} + std::string S; + llvm::Optional 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> R; + CustomStruct ExpectedStruct = {"foo", 42, true}; + std::map> 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 must parse as the correct type if present. + EXPECT_FALSE(fromJSON(obj{{"str", 1}, {"int", "string"}}, V)) + << "Wrong type for Optional " << V; +} + } // namespace } // namespace json } // namespace clangd