[clang-tools-extra] r319478 - [clangd] New conventions for JSON-marshalling functions, centralize machinery
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 30 13:32:29 PST 2017
Author: sammccall
Date: Thu Nov 30 13:32:29 2017
New Revision: 319478
URL: http://llvm.org/viewvc/llvm-project?rev=319478&view=rev
Log:
[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
Modified:
clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
clang-tools-extra/trunk/clangd/JSONExpr.h
clang-tools-extra/trunk/clangd/Protocol.cpp
clang-tools-extra/trunk/clangd/Protocol.h
clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
clang-tools-extra/trunk/test/clangd/trace.test
clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Thu Nov 30 13:32:29 2017
@@ -117,7 +117,7 @@ void ClangdLSPServer::onCommand(Ctx C, E
// 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, Re
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,
Modified: clang-tools-extra/trunk/clangd/JSONExpr.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/JSONExpr.h?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/JSONExpr.h (original)
+++ clang-tools-extra/trunk/clangd/JSONExpr.h Thu Nov 30 13:32:29 2017
@@ -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:
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> {
Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Thu Nov 30 13:32:29 2017
@@ -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;
+}
+
+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<int>(FileChangeType::Created) ||
+ *T > static_cast<int>(FileChangeType::Deleted))
+ return false;
+ Out = static_cast<FileChangeType>(*T);
+ return true;
+ }
+ return false;
}
-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;
-}
-
-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;
-}
-
-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;
-}
-
-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;
-}
-
-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;
-}
-
-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;
-}
-
-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, 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>
-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>
-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>
-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> 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>
-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>
-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> 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>
-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
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
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
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
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
};
}
-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
Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Thu Nov 30 13:32:29 2017
@@ -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
Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Thu Nov 30 13:32:29 2017
@@ -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");
}
Modified: clang-tools-extra/trunk/test/clangd/trace.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/trace.test?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/trace.test (original)
+++ clang-tools-extra/trunk/test/clangd/trace.test Thu Nov 30 13:32:29 2017
@@ -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: },
Modified: clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp?rev=319478&r1=319477&r2=319478&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/JSONExprTests.cpp Thu Nov 30 13:32:29 2017
@@ -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
More information about the cfe-commits
mailing list