[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
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 233d86c..1d38184 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/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<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 @@
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 @@
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 @@
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> 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