diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -180,22 +180,37 @@ std::unique_ptr MsgHandler; std::mutex TranspWriter; + template + static Expected parse(const llvm::json::Value &Raw, + llvm::StringRef PayloadName, + llvm::StringRef PayloadKind) { + T Result; + llvm::json::Path::Root Root; + if (!fromJSON(Raw, Result, Root)) { + elog("Failed to decode {0} {1}", PayloadName, PayloadKind); + // Dump the relevant parts of the broken message. + std::string Context; + llvm::raw_string_ostream OS(Context); + Root.printErrorContext(Raw, OS); + vlog("{0}", OS.str()); + // Report the error (e.g. to the client). + return llvm::make_error( + llvm::formatv("failed to decode {0} {1}", PayloadName, PayloadKind), + ErrorCode::InvalidParams); + } + return std::move(Result); + } + template void call(StringRef Method, llvm::json::Value Params, Callback CB) { // Wrap the callback with LSP conversion and error-handling. auto HandleReply = - [CB = std::move(CB), Ctx = Context::current().clone()]( + [CB = std::move(CB), Ctx = Context::current().clone(), + Method = Method.str()]( llvm::Expected RawResponse) mutable { - Response Rsp; - if (!RawResponse) { - CB(RawResponse.takeError()); - } else if (fromJSON(*RawResponse, Rsp)) { - CB(std::move(Rsp)); - } else { - elog("Failed to decode {0} response", *RawResponse); - CB(llvm::make_error("failed to decode response", - ErrorCode::InvalidParams)); - } + if (!RawResponse) + return CB(RawResponse.takeError()); + CB(parse(*RawResponse, Method, "response")); }; callRaw(Method, std::move(Params), std::move(HandleReply)); } diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -247,14 +247,10 @@ void (ClangdLSPServer::*Handler)(const Param &, Callback)) { Calls[Method] = [Method, Handler, this](llvm::json::Value RawParams, ReplyOnce Reply) { - Param P; - if (fromJSON(RawParams, P)) { - (Server.*Handler)(P, std::move(Reply)); - } else { - elog("Failed to decode {0} request.", Method); - Reply(llvm::make_error("failed to decode request", - ErrorCode::InvalidRequest)); - } + auto P = parse(RawParams, Method, "request"); + if (!P) + return Reply(P.takeError()); + (Server.*Handler)(*P, std::move(Reply)); }; } @@ -292,14 +288,12 @@ void (ClangdLSPServer::*Handler)(const Param &)) { Notifications[Method] = [Method, Handler, this](llvm::json::Value RawParams) { - Param P; - if (!fromJSON(RawParams, P)) { - elog("Failed to decode {0} request.", Method); - return; - } + llvm::Expected P = parse(RawParams, Method, "request"); + if (!P) + return llvm::consumeError(P.takeError()); trace::Span Tracer(Method, LSPLatency); SPAN_ATTACH(Tracer, "Params", RawParams); - (Server.*Handler)(P); + (Server.*Handler)(*P); }; } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -116,14 +116,15 @@ /// Serialize/deserialize \p URIForFile to/from a string URI. llvm::json::Value toJSON(const URIForFile &U); -bool fromJSON(const llvm::json::Value &, URIForFile &); +bool fromJSON(const llvm::json::Value &, URIForFile &, llvm::json::Path); struct TextDocumentIdentifier { /// The text document's URI. URIForFile uri; }; llvm::json::Value toJSON(const TextDocumentIdentifier &); -bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &); +bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &, + llvm::json::Path); struct VersionedTextDocumentIdentifier : public TextDocumentIdentifier { /// The version number of this document. If a versioned text document @@ -139,7 +140,8 @@ llvm::Optional version; }; llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &); -bool fromJSON(const llvm::json::Value &, VersionedTextDocumentIdentifier &); +bool fromJSON(const llvm::json::Value &, VersionedTextDocumentIdentifier &, + llvm::json::Path); struct Position { /// Line position in a document (zero-based). @@ -166,7 +168,7 @@ std::tie(RHS.line, RHS.character); } }; -bool fromJSON(const llvm::json::Value &, Position &); +bool fromJSON(const llvm::json::Value &, Position &, llvm::json::Path); llvm::json::Value toJSON(const Position &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Position &); @@ -192,7 +194,7 @@ return start <= Rng.start && Rng.end <= end; } }; -bool fromJSON(const llvm::json::Value &, Range &); +bool fromJSON(const llvm::json::Value &, Range &, llvm::json::Path); llvm::json::Value toJSON(const Range &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Range &); @@ -228,7 +230,7 @@ inline bool operator==(const TextEdit &L, const TextEdit &R) { return std::tie(L.newText, L.range) == std::tie(R.newText, R.range); } -bool fromJSON(const llvm::json::Value &, TextEdit &); +bool fromJSON(const llvm::json::Value &, TextEdit &, llvm::json::Path); llvm::json::Value toJSON(const TextEdit &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &); @@ -248,17 +250,19 @@ /// The content of the opened text document. std::string text; }; -bool fromJSON(const llvm::json::Value &, TextDocumentItem &); +bool fromJSON(const llvm::json::Value &, TextDocumentItem &, llvm::json::Path); enum class TraceLevel { Off = 0, Messages = 1, Verbose = 2, }; -bool fromJSON(const llvm::json::Value &E, TraceLevel &Out); +bool fromJSON(const llvm::json::Value &E, TraceLevel &Out, llvm::json::Path); struct NoParams {}; -inline bool fromJSON(const llvm::json::Value &, NoParams &) { return true; } +inline bool fromJSON(const llvm::json::Value &, NoParams &, llvm::json::Path) { + return true; +} using InitializedParams = NoParams; using ShutdownParams = NoParams; using ExitParams = NoParams; @@ -306,13 +310,15 @@ Operator = 24, TypeParameter = 25, }; -bool fromJSON(const llvm::json::Value &, CompletionItemKind &); +bool fromJSON(const llvm::json::Value &, CompletionItemKind &, + llvm::json::Path); constexpr auto CompletionItemKindMin = static_cast(CompletionItemKind::Text); constexpr auto CompletionItemKindMax = static_cast(CompletionItemKind::TypeParameter); using CompletionItemKindBitset = std::bitset; -bool fromJSON(const llvm::json::Value &, CompletionItemKindBitset &); +bool fromJSON(const llvm::json::Value &, CompletionItemKindBitset &, + llvm::json::Path); CompletionItemKind adjustKindToCapability(CompletionItemKind Kind, CompletionItemKindBitset &SupportedCompletionItemKinds); @@ -346,11 +352,11 @@ Operator = 25, TypeParameter = 26 }; -bool fromJSON(const llvm::json::Value &, SymbolKind &); +bool fromJSON(const llvm::json::Value &, SymbolKind &, llvm::json::Path); constexpr auto SymbolKindMin = static_cast(SymbolKind::File); constexpr auto SymbolKindMax = static_cast(SymbolKind::TypeParameter); using SymbolKindBitset = std::bitset; -bool fromJSON(const llvm::json::Value &, SymbolKindBitset &); +bool fromJSON(const llvm::json::Value &, SymbolKindBitset &, llvm::json::Path); SymbolKind adjustKindToCapability(SymbolKind Kind, SymbolKindBitset &supportedSymbolKinds); @@ -372,7 +378,7 @@ UTF32, }; llvm::json::Value toJSON(const OffsetEncoding &); -bool fromJSON(const llvm::json::Value &, OffsetEncoding &); +bool fromJSON(const llvm::json::Value &, OffsetEncoding &, llvm::json::Path); llvm::raw_ostream &operator<<(llvm::raw_ostream &, OffsetEncoding); // Describes the content type that a client supports in various result literals @@ -381,7 +387,7 @@ PlainText, Markdown, }; -bool fromJSON(const llvm::json::Value &, MarkupKind &); +bool fromJSON(const llvm::json::Value &, MarkupKind &, llvm::json::Path); llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, MarkupKind); // This struct doesn't mirror LSP! @@ -470,7 +476,8 @@ /// window.implicitWorkDoneProgressCreate bool ImplicitProgressCreation = false; }; -bool fromJSON(const llvm::json::Value &, ClientCapabilities &); +bool fromJSON(const llvm::json::Value &, ClientCapabilities &, + llvm::json::Path); /// Clangd extension that's used in the 'compilationDatabaseChanges' in /// workspace/didChangeConfiguration to record updates to the in-memory @@ -479,7 +486,8 @@ std::string workingDirectory; std::vector compilationCommand; }; -bool fromJSON(const llvm::json::Value &, ClangdCompileCommand &); +bool fromJSON(const llvm::json::Value &, ClangdCompileCommand &, + llvm::json::Path); /// Clangd extension: parameters configurable at any time, via the /// `workspace/didChangeConfiguration` notification. @@ -489,7 +497,8 @@ // The key of the map is a file name. std::map compilationDatabaseChanges; }; -bool fromJSON(const llvm::json::Value &, ConfigurationSettings &); +bool fromJSON(const llvm::json::Value &, ConfigurationSettings &, + llvm::json::Path); /// Clangd extension: parameters configurable at `initialize` time. /// LSP defines this type as `any`. @@ -507,7 +516,8 @@ /// Clients supports show file status for textDocument/clangd.fileStatus. bool FileStatus = false; }; -bool fromJSON(const llvm::json::Value &, InitializationOptions &); +bool fromJSON(const llvm::json::Value &, InitializationOptions &, + llvm::json::Path); struct InitializeParams { /// The process Id of the parent process that started @@ -539,7 +549,7 @@ /// User-provided initialization options. InitializationOptions initializationOptions; }; -bool fromJSON(const llvm::json::Value &, InitializeParams &); +bool fromJSON(const llvm::json::Value &, InitializeParams &, llvm::json::Path); struct WorkDoneProgressCreateParams { /// The token to be used to report progress. @@ -650,19 +660,22 @@ /// The document that was opened. TextDocumentItem textDocument; }; -bool fromJSON(const llvm::json::Value &, DidOpenTextDocumentParams &); +bool fromJSON(const llvm::json::Value &, DidOpenTextDocumentParams &, + llvm::json::Path); struct DidCloseTextDocumentParams { /// The document that was closed. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &); +bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &, + llvm::json::Path); struct DidSaveTextDocumentParams { /// The document that was saved. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, DidSaveTextDocumentParams &); +bool fromJSON(const llvm::json::Value &, DidSaveTextDocumentParams &, + llvm::json::Path); struct TextDocumentContentChangeEvent { /// The range of the document that changed. @@ -674,7 +687,8 @@ /// The new text of the range/document. std::string text; }; -bool fromJSON(const llvm::json::Value &, TextDocumentContentChangeEvent &); +bool fromJSON(const llvm::json::Value &, TextDocumentContentChangeEvent &, + llvm::json::Path); struct DidChangeTextDocumentParams { /// The document that did change. The version number points @@ -697,7 +711,8 @@ /// This is a clangd extension. bool forceRebuild = false; }; -bool fromJSON(const llvm::json::Value &, DidChangeTextDocumentParams &); +bool fromJSON(const llvm::json::Value &, DidChangeTextDocumentParams &, + llvm::json::Path); enum class FileChangeType { /// The file got created. @@ -707,7 +722,8 @@ /// The file got deleted. Deleted = 3 }; -bool fromJSON(const llvm::json::Value &E, FileChangeType &Out); +bool fromJSON(const llvm::json::Value &E, FileChangeType &Out, + llvm::json::Path); struct FileEvent { /// The file's URI. @@ -715,18 +731,20 @@ /// The change type. FileChangeType type = FileChangeType::Created; }; -bool fromJSON(const llvm::json::Value &, FileEvent &); +bool fromJSON(const llvm::json::Value &, FileEvent &, llvm::json::Path); struct DidChangeWatchedFilesParams { /// The actual file events. std::vector changes; }; -bool fromJSON(const llvm::json::Value &, DidChangeWatchedFilesParams &); +bool fromJSON(const llvm::json::Value &, DidChangeWatchedFilesParams &, + llvm::json::Path); struct DidChangeConfigurationParams { ConfigurationSettings settings; }; -bool fromJSON(const llvm::json::Value &, DidChangeConfigurationParams &); +bool fromJSON(const llvm::json::Value &, DidChangeConfigurationParams &, + llvm::json::Path); // Note: we do not parse FormattingOptions for *FormattingParams. // In general, we use a clang-format style detected from common mechanisms @@ -743,7 +761,8 @@ /// The range to format Range range; }; -bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &); +bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &, + llvm::json::Path); struct DocumentOnTypeFormattingParams { /// The document to format. @@ -755,19 +774,22 @@ /// The character that has been typed. std::string ch; }; -bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &); +bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &, + llvm::json::Path); struct DocumentFormattingParams { /// The document to format. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &); +bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &, + llvm::json::Path); struct DocumentSymbolParams { // The text document to find symbols in. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, DocumentSymbolParams &); +bool fromJSON(const llvm::json::Value &, DocumentSymbolParams &, + llvm::json::Path); /// Represents a related message and source code location for a diagnostic. /// This should be used to point to code locations that cause or related to a @@ -826,7 +848,7 @@ return std::tie(LHS.range, LHS.message) < std::tie(RHS.range, RHS.message); } }; -bool fromJSON(const llvm::json::Value &, Diagnostic &); +bool fromJSON(const llvm::json::Value &, Diagnostic &, llvm::json::Path); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Diagnostic &); struct PublishDiagnosticsParams { @@ -843,7 +865,7 @@ /// An array of diagnostics. std::vector diagnostics; }; -bool fromJSON(const llvm::json::Value &, CodeActionContext &); +bool fromJSON(const llvm::json::Value &, CodeActionContext &, llvm::json::Path); struct CodeActionParams { /// The document in which the command was invoked. @@ -855,7 +877,7 @@ /// Context carrying additional information. CodeActionContext context; }; -bool fromJSON(const llvm::json::Value &, CodeActionParams &); +bool fromJSON(const llvm::json::Value &, CodeActionParams &, llvm::json::Path); struct WorkspaceEdit { /// Holds changes to existing resources. @@ -864,7 +886,7 @@ /// Note: "documentChanges" is not currently used because currently there is /// no support for versioned edits. }; -bool fromJSON(const llvm::json::Value &, WorkspaceEdit &); +bool fromJSON(const llvm::json::Value &, WorkspaceEdit &, llvm::json::Path); llvm::json::Value toJSON(const WorkspaceEdit &WE); /// Arguments for the 'applyTweak' command. The server sends these commands as a @@ -879,7 +901,7 @@ /// ID of the tweak that should be executed. Corresponds to Tweak::id(). std::string tweakID; }; -bool fromJSON(const llvm::json::Value &, TweakArgs &); +bool fromJSON(const llvm::json::Value &, TweakArgs &, llvm::json::Path); llvm::json::Value toJSON(const TweakArgs &A); /// Exact commands are not specified in the protocol so we define the @@ -903,7 +925,8 @@ llvm::Optional workspaceEdit; llvm::Optional tweakArgs; }; -bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &); +bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &, + llvm::json::Path); struct Command : public ExecuteCommandParams { std::string title; @@ -1014,7 +1037,8 @@ /// A non-empty query string std::string query; }; -bool fromJSON(const llvm::json::Value &, WorkspaceSymbolParams &); +bool fromJSON(const llvm::json::Value &, WorkspaceSymbolParams &, + llvm::json::Path); struct ApplyWorkspaceEditParams { WorkspaceEdit edit; @@ -1025,7 +1049,8 @@ bool applied = true; llvm::Optional failureReason; }; -bool fromJSON(const llvm::json::Value &, ApplyWorkspaceEditResponse &); +bool fromJSON(const llvm::json::Value &, ApplyWorkspaceEditResponse &, + llvm::json::Path); struct TextDocumentPositionParams { /// The text document. @@ -1034,7 +1059,8 @@ /// The position inside the text document. Position position; }; -bool fromJSON(const llvm::json::Value &, TextDocumentPositionParams &); +bool fromJSON(const llvm::json::Value &, TextDocumentPositionParams &, + llvm::json::Path); enum class CompletionTriggerKind { /// Completion was triggered by typing an identifier (24x7 code @@ -1054,12 +1080,12 @@ /// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` std::string triggerCharacter; }; -bool fromJSON(const llvm::json::Value &, CompletionContext &); +bool fromJSON(const llvm::json::Value &, CompletionContext &, llvm::json::Path); struct CompletionParams : TextDocumentPositionParams { CompletionContext context; }; -bool fromJSON(const llvm::json::Value &, CompletionParams &); +bool fromJSON(const llvm::json::Value &, CompletionParams &, llvm::json::Path); struct MarkupContent { MarkupKind kind = MarkupKind::PlainText; @@ -1237,7 +1263,7 @@ /// The new name of the symbol. std::string newName; }; -bool fromJSON(const llvm::json::Value &, RenameParams &); +bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path); enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; @@ -1268,7 +1294,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &); enum class TypeHierarchyDirection { Children = 0, Parents = 1, Both = 2 }; -bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out); +bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out, + llvm::json::Path); /// The type hierarchy params is an extension of the /// `TextDocumentPositionsParams` with optional properties which can be used to @@ -1280,7 +1307,8 @@ /// The direction of the hierarchy levels to resolve. TypeHierarchyDirection direction = TypeHierarchyDirection::Parents; }; -bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &); +bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &, + llvm::json::Path); struct TypeHierarchyItem { /// The human readable name of the hierarchy item. @@ -1326,7 +1354,7 @@ }; llvm::json::Value toJSON(const TypeHierarchyItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &); -bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &); +bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &, llvm::json::Path); /// Parameters for the `typeHierarchy/resolve` request. struct ResolveTypeHierarchyItemParams { @@ -1339,12 +1367,13 @@ /// The direction of the hierarchy levels to resolve. TypeHierarchyDirection direction; }; -bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &); +bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &, + llvm::json::Path); struct ReferenceParams : public TextDocumentPositionParams { // For now, no options like context.includeDeclaration are supported. }; -bool fromJSON(const llvm::json::Value &, ReferenceParams &); +bool fromJSON(const llvm::json::Value &, ReferenceParams &, llvm::json::Path); /// Clangd extension: indicates the current state of the file in clangd, /// sent from server via the `textDocument/clangd.fileStatus` notification. @@ -1394,7 +1423,8 @@ /// The text document. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, SemanticTokensParams &); +bool fromJSON(const llvm::json::Value &, SemanticTokensParams &, + llvm::json::Path); /// Body of textDocument/semanticTokens/full/delta request. /// Requests the changes in semantic tokens since a previous response. @@ -1404,7 +1434,8 @@ /// The previous result id. std::string previousResultId; }; -bool fromJSON(const llvm::json::Value &Params, SemanticTokensDeltaParams &R); +bool fromJSON(const llvm::json::Value &Params, SemanticTokensDeltaParams &R, + llvm::json::Path); /// Describes a a replacement of a contiguous range of semanticTokens. struct SemanticTokensEdit { @@ -1462,7 +1493,8 @@ /// The positions inside the text document. std::vector positions; }; -bool fromJSON(const llvm::json::Value &, SelectionRangeParams &); +bool fromJSON(const llvm::json::Value &, SelectionRangeParams &, + llvm::json::Path); struct SelectionRange { /** @@ -1482,7 +1514,8 @@ /// The document to provide document links for. TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, DocumentLinkParams &); +bool fromJSON(const llvm::json::Value &, DocumentLinkParams &, + llvm::json::Path); /// A range in a text document that links to an internal or external resource, /// like another text document or a web site. @@ -1515,7 +1548,8 @@ struct FoldingRangeParams { TextDocumentIdentifier textDocument; }; -bool fromJSON(const llvm::json::Value &, FoldingRangeParams &); +bool fromJSON(const llvm::json::Value &, FoldingRangeParams &, + llvm::json::Path); /// Stores information about a region of code that can be folded. struct FoldingRange { diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -51,22 +51,22 @@ return URIForFile(std::move(*Resolved)); } -bool fromJSON(const llvm::json::Value &E, URIForFile &R) { +bool fromJSON(const llvm::json::Value &E, URIForFile &R, llvm::json::Path P) { if (auto S = E.getAsString()) { auto Parsed = URI::parse(*S); if (!Parsed) { - elog("Failed to parse URI {0}: {1}", *S, Parsed.takeError()); + P.report("failed to parse URI"); return false; } if (Parsed->scheme() != "file" && Parsed->scheme() != "test") { - elog("Clangd only supports 'file' URI scheme for workspace files: {0}", - *S); + P.report("clangd only supports 'file' URI scheme for workspace files"); return false; } // "file" and "test" schemes do not require hint path. auto U = URIForFile::fromURI(*Parsed, /*HintPath=*/""); if (!U) { - elog("{0}", U.takeError()); + P.report("unresolvable URI"); + consumeError(U.takeError()); return false; } R = std::move(*U); @@ -85,8 +85,9 @@ return llvm::json::Object{{"uri", R.uri}}; } -bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("uri", R.uri); } @@ -97,14 +98,15 @@ } bool fromJSON(const llvm::json::Value &Params, - VersionedTextDocumentIdentifier &R) { - llvm::json::ObjectMapper O(Params); - return fromJSON(Params, static_cast(R)) && O && + VersionedTextDocumentIdentifier &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return fromJSON(Params, static_cast(R), P) && O && O.map("version", R.version); } -bool fromJSON(const llvm::json::Value &Params, Position &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, Position &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("line", R.line) && O.map("character", R.character); } @@ -119,8 +121,8 @@ return OS << P.line << ':' << P.character; } -bool fromJSON(const llvm::json::Value &Params, Range &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, Range &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("start", R.start) && O.map("end", R.end); } @@ -146,14 +148,16 @@ return OS << L.range << '@' << L.uri; } -bool fromJSON(const llvm::json::Value &Params, TextDocumentItem &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TextDocumentItem &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) && O.map("version", R.version) && O.map("text", R.text); } -bool fromJSON(const llvm::json::Value &Params, TextEdit &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TextEdit &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("range", R.range) && O.map("newText", R.newText); } @@ -170,7 +174,7 @@ return OS << '"'; } -bool fromJSON(const llvm::json::Value &E, TraceLevel &Out) { +bool fromJSON(const llvm::json::Value &E, TraceLevel &Out, llvm::json::Path P) { if (auto S = E.getAsString()) { if (*S == "off") { Out = TraceLevel::Off; @@ -186,7 +190,7 @@ return false; } -bool fromJSON(const llvm::json::Value &E, SymbolKind &Out) { +bool fromJSON(const llvm::json::Value &E, SymbolKind &Out, llvm::json::Path P) { if (auto T = E.getAsInteger()) { if (*T < static_cast(SymbolKind::File) || *T > static_cast(SymbolKind::TypeParameter)) @@ -197,11 +201,12 @@ return false; } -bool fromJSON(const llvm::json::Value &E, SymbolKindBitset &Out) { +bool fromJSON(const llvm::json::Value &E, SymbolKindBitset &Out, + llvm::json::Path P) { if (auto *A = E.getAsArray()) { for (size_t I = 0; I < A->size(); ++I) { SymbolKind KindOut; - if (fromJSON((*A)[I], KindOut)) + if (fromJSON((*A)[I], KindOut, P.index(I))) Out.set(size_t(KindOut)); } return true; @@ -286,10 +291,13 @@ llvm_unreachable("invalid symbol kind"); } -bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) { +bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R, + llvm::json::Path P) { const llvm::json::Object *O = Params.getAsObject(); - if (!O) + if (!O) { + P.report("expected object"); return false; + } if (auto *TextDocument = O->getObject("textDocument")) { if (auto *SemanticHighlighting = TextDocument->getObject("semanticHighlightingCapabilities")) { @@ -313,7 +321,7 @@ R.CompletionSnippets = *SnippetSupport; if (auto DocumentationFormat = Item->getArray("documentationFormat")) { for (const auto &Format : *DocumentationFormat) { - if (fromJSON(Format, R.CompletionDocumentationFormat)) + if (fromJSON(Format, R.CompletionDocumentationFormat, P)) break; } } @@ -321,7 +329,11 @@ if (auto *ItemKind = Completion->getObject("completionItemKind")) { if (auto *ValueSet = ItemKind->get("valueSet")) { R.CompletionItemKinds.emplace(); - if (!fromJSON(*ValueSet, *R.CompletionItemKinds)) + if (!fromJSON(*ValueSet, *R.CompletionItemKinds, + P.field("textDocument") + .field("completion") + .field("completionItemKind") + .field("valueSet"))) return false; } } @@ -340,7 +352,7 @@ if (auto *Hover = TextDocument->getObject("hover")) { if (auto *ContentFormat = Hover->getArray("contentFormat")) { for (const auto &Format : *ContentFormat) { - if (fromJSON(Format, R.HoverContentFormat)) + if (fromJSON(Format, R.HoverContentFormat, P)) break; } } @@ -364,7 +376,11 @@ if (auto *SymbolKind = Symbol->getObject("symbolKind")) { if (auto *ValueSet = SymbolKind->get("valueSet")) { R.WorkspaceSymbolKinds.emplace(); - if (!fromJSON(*ValueSet, *R.WorkspaceSymbolKinds)) + if (!fromJSON(*ValueSet, *R.WorkspaceSymbolKinds, + P.field("workspace") + .field("symbol") + .field("symbolKind") + .field("valueSet"))) return false; } } @@ -378,14 +394,16 @@ } if (auto *OffsetEncoding = O->get("offsetEncoding")) { R.offsetEncoding.emplace(); - if (!fromJSON(*OffsetEncoding, *R.offsetEncoding)) + if (!fromJSON(*OffsetEncoding, *R.offsetEncoding, + P.field("offsetEncoding"))) return false; } return true; } -bool fromJSON(const llvm::json::Value &Params, InitializeParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, InitializeParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O) return false; // We deliberately don't fail if we can't parse individual fields. @@ -445,23 +463,27 @@ return llvm::json::Object{{"type", R.type}, {"message", R.message}}; } -bool fromJSON(const llvm::json::Value &Params, DidOpenTextDocumentParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DidOpenTextDocumentParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } -bool fromJSON(const llvm::json::Value &Params, DidCloseTextDocumentParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DidCloseTextDocumentParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } -bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } -bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O) return false; O.map("forceRebuild", R.forceRebuild); // Optional clangd extension. @@ -470,7 +492,8 @@ O.map("wantDiagnostics", R.wantDiagnostics); } -bool fromJSON(const llvm::json::Value &E, FileChangeType &Out) { +bool fromJSON(const llvm::json::Value &E, FileChangeType &Out, + llvm::json::Path P) { if (auto T = E.getAsInteger()) { if (*T < static_cast(FileChangeType::Created) || *T > static_cast(FileChangeType::Deleted)) @@ -481,43 +504,47 @@ return false; } -bool fromJSON(const llvm::json::Value &Params, FileEvent &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, FileEvent &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("uri", R.uri) && O.map("type", R.type); } -bool fromJSON(const llvm::json::Value &Params, DidChangeWatchedFilesParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DidChangeWatchedFilesParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("changes", R.changes); } bool fromJSON(const llvm::json::Value &Params, - TextDocumentContentChangeEvent &R) { - llvm::json::ObjectMapper O(Params); + TextDocumentContentChangeEvent &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("range", R.range) && O.map("rangeLength", R.rangeLength) && O.map("text", R.text); } -bool fromJSON(const llvm::json::Value &Params, - DocumentRangeFormattingParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("range", R.range); } bool fromJSON(const llvm::json::Value &Params, - DocumentOnTypeFormattingParams &R) { - llvm::json::ObjectMapper O(Params); + DocumentOnTypeFormattingParams &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("position", R.position) && O.map("ch", R.ch); } -bool fromJSON(const llvm::json::Value &Params, DocumentFormattingParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DocumentFormattingParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } -bool fromJSON(const llvm::json::Value &Params, DocumentSymbolParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DocumentSymbolParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } @@ -548,8 +575,9 @@ return std::move(Diag); } -bool fromJSON(const llvm::json::Value &Params, Diagnostic &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, Diagnostic &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O || !O.map("range", R.range) || !O.map("message", R.message)) return false; O.map("severity", R.severity); @@ -569,8 +597,9 @@ return std::move(Result); } -bool fromJSON(const llvm::json::Value &Params, CodeActionContext &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, CodeActionContext &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("diagnostics", R.diagnostics); } @@ -596,14 +625,16 @@ return OS << '(' << D.severity << "): " << D.message << "]"; } -bool fromJSON(const llvm::json::Value &Params, CodeActionParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, CodeActionParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("range", R.range) && O.map("context", R.context); } -bool fromJSON(const llvm::json::Value &Params, WorkspaceEdit &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, WorkspaceEdit &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("changes", R.changes); } @@ -612,18 +643,21 @@ const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_TWEAK = "clangd.applyTweak"; -bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O || !O.map("command", R.command)) return false; auto Args = Params.getAsObject()->getArray("arguments"); if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) { return Args && Args->size() == 1 && - fromJSON(Args->front(), R.workspaceEdit); + fromJSON(Args->front(), R.workspaceEdit, + P.field("arguments").index(0)); } if (R.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK) - return Args && Args->size() == 1 && fromJSON(Args->front(), R.tweakArgs); + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.tweakArgs, P.field("arguments").index(0)); return false; // Unrecognized command. } @@ -681,8 +715,9 @@ return O; } -bool fromJSON(const llvm::json::Value &Params, WorkspaceSymbolParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, WorkspaceSymbolParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("query", R.query); } @@ -741,8 +776,9 @@ return llvm::json::Object{{"changes", std::move(FileChanges)}}; } -bool fromJSON(const llvm::json::Value &Params, TweakArgs &A) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TweakArgs &A, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("file", A.file) && O.map("selection", A.selection) && O.map("tweakID", A.tweakID); } @@ -756,23 +792,25 @@ return llvm::json::Object{{"edit", Params.edit}}; } -bool fromJSON(const llvm::json::Value &Response, - ApplyWorkspaceEditResponse &R) { - llvm::json::ObjectMapper O(Response); +bool fromJSON(const llvm::json::Value &Response, ApplyWorkspaceEditResponse &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Response, P); if (!O || !O.map("applied", R.applied)) return false; O.map("failureReason", R.failureReason); return true; } -bool fromJSON(const llvm::json::Value &Params, TextDocumentPositionParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TextDocumentPositionParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("position", R.position); } -bool fromJSON(const llvm::json::Value &Params, CompletionContext &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, CompletionContext &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O) return false; @@ -782,15 +820,16 @@ R.triggerKind = static_cast(TriggerKind); if (auto *TC = Params.getAsObject()->get("triggerCharacter")) - return fromJSON(*TC, R.triggerCharacter); + return fromJSON(*TC, R.triggerCharacter, P.field("triggerCharacter")); return true; } -bool fromJSON(const llvm::json::Value &Params, CompletionParams &R) { - if (!fromJSON(Params, static_cast(R))) +bool fromJSON(const llvm::json::Value &Params, CompletionParams &R, + llvm::json::Path P) { + if (!fromJSON(Params, static_cast(R), P)) return false; if (auto *Context = Params.getAsObject()->get("context")) - return fromJSON(*Context, R.context); + return fromJSON(*Context, R.context, P.field("context")); return true; } @@ -804,7 +843,7 @@ llvm_unreachable("Invalid MarkupKind"); } -bool fromJSON(const llvm::json::Value &V, MarkupKind &K) { +bool fromJSON(const llvm::json::Value &V, MarkupKind &K, llvm::json::Path P) { auto Str = V.getAsString(); if (!Str) { elog("Failed to parse markup kind: expected a string"); @@ -844,7 +883,8 @@ return std::move(Result); } -bool fromJSON(const llvm::json::Value &E, CompletionItemKind &Out) { +bool fromJSON(const llvm::json::Value &E, CompletionItemKind &Out, + llvm::json::Path P) { if (auto T = E.getAsInteger()) { if (*T < static_cast(CompletionItemKind::Text) || *T > static_cast(CompletionItemKind::TypeParameter)) @@ -877,11 +917,12 @@ } } -bool fromJSON(const llvm::json::Value &E, CompletionItemKindBitset &Out) { +bool fromJSON(const llvm::json::Value &E, CompletionItemKindBitset &Out, + llvm::json::Path P) { if (auto *A = E.getAsArray()) { for (size_t I = 0; I < A->size(); ++I) { CompletionItemKind KindOut; - if (fromJSON((*A)[I], KindOut)) + if (fromJSON((*A)[I], KindOut, P.index(I))) Out.set(size_t(KindOut)); } return true; @@ -976,8 +1017,9 @@ }; } -bool fromJSON(const llvm::json::Value &Params, RenameParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, RenameParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("position", R.position) && O.map("newName", R.newName); } @@ -1038,13 +1080,15 @@ return std::move(Result); } -bool fromJSON(const llvm::json::Value &Params, SemanticTokensParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, SemanticTokensParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } -bool fromJSON(const llvm::json::Value &Params, SemanticTokensDeltaParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, SemanticTokensDeltaParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("previousResultId", R.previousResultId); } @@ -1060,39 +1104,42 @@ } bool fromJSON(const llvm::json::Value &Params, - DidChangeConfigurationParams &CCP) { - llvm::json::ObjectMapper O(Params); + DidChangeConfigurationParams &CCP, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("settings", CCP.settings); } -bool fromJSON(const llvm::json::Value &Params, - ClangdCompileCommand &CDbUpdate) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, ClangdCompileCommand &CDbUpdate, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("workingDirectory", CDbUpdate.workingDirectory) && O.map("compilationCommand", CDbUpdate.compilationCommand); } -bool fromJSON(const llvm::json::Value &Params, ConfigurationSettings &S) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, ConfigurationSettings &S, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O) return true; // 'any' type in LSP. O.map("compilationDatabaseChanges", S.compilationDatabaseChanges); return true; } -bool fromJSON(const llvm::json::Value &Params, InitializationOptions &Opts) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, InitializationOptions &Opts, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); if (!O) return true; // 'any' type in LSP. - fromJSON(Params, Opts.ConfigSettings); + fromJSON(Params, Opts.ConfigSettings, P); O.map("compilationDatabasePath", Opts.compilationDatabasePath); O.map("fallbackFlags", Opts.fallbackFlags); O.map("clangdFileStatus", Opts.FileStatus); return true; } -bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) { +bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out, + llvm::json::Path P) { auto T = E.getAsInteger(); if (!T) return false; @@ -1103,8 +1150,9 @@ return true; } -bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && O.map("position", R.position) && O.map("resolve", R.resolve) && O.map("direction", R.direction); @@ -1135,8 +1183,9 @@ return std::move(Result); } -bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); // Required fields. if (!(O && O.map("name", I.name) && O.map("kind", I.kind) && @@ -1156,15 +1205,16 @@ } bool fromJSON(const llvm::json::Value &Params, - ResolveTypeHierarchyItemParams &P) { - llvm::json::ObjectMapper O(Params); - return O && O.map("item", P.item) && O.map("resolve", P.resolve) && - O.map("direction", P.direction); + ResolveTypeHierarchyItemParams &R, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("item", R.item) && O.map("resolve", R.resolve) && + O.map("direction", R.direction); } -bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) { +bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R, + llvm::json::Path P) { TextDocumentPositionParams &Base = R; - return fromJSON(Params, Base); + return fromJSON(Params, Base, P); } static const char *toString(OffsetEncoding OE) { @@ -1181,7 +1231,8 @@ llvm_unreachable("Unknown clang.clangd.OffsetEncoding"); } llvm::json::Value toJSON(const OffsetEncoding &OE) { return toString(OE); } -bool fromJSON(const llvm::json::Value &V, OffsetEncoding &OE) { +bool fromJSON(const llvm::json::Value &V, OffsetEncoding &OE, + llvm::json::Path P) { auto Str = V.getAsString(); if (!Str) return false; @@ -1215,10 +1266,11 @@ }; } -bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &P) { - llvm::json::ObjectMapper O(Params); - return O && O.map("textDocument", P.textDocument) && - O.map("positions", P.positions); +bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("textDocument", S.textDocument) && + O.map("positions", S.positions); } llvm::json::Value toJSON(const SelectionRange &Out) { @@ -1229,8 +1281,9 @@ return llvm::json::Object{{"range", Out.range}}; } -bool fromJSON(const llvm::json::Value &Params, DocumentLinkParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, DocumentLinkParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } @@ -1241,8 +1294,9 @@ }; } -bool fromJSON(const llvm::json::Value &Params, FoldingRangeParams &R) { - llvm::json::ObjectMapper O(Params); +bool fromJSON(const llvm::json::Value &Params, FoldingRangeParams &R, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument); } diff --git a/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp b/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp --- a/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp +++ b/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp @@ -56,8 +56,10 @@ for (const auto &Item : *JSONArray->getAsArray()) { FuzzyFindRequest Request; // Panic if the provided file couldn't be parsed. - if (!fromJSON(Item, Request)) { - llvm::errs() << "Error when deserializing request: " << Item << '\n'; + llvm::json::Path::Root Root("FuzzyFindRequest"); + if (!fromJSON(Item, Request, Root)) { + llvm::errs() << llvm::toString(Root.getError()) << "\n"; + Root.printErrorContext(Item, llvm::errs()); exit(1); } Requests.push_back(Request); diff --git a/clang-tools-extra/clangd/index/Index.h b/clang-tools-extra/clangd/index/Index.h --- a/clang-tools-extra/clangd/index/Index.h +++ b/clang-tools-extra/clangd/index/Index.h @@ -57,7 +57,8 @@ } bool operator!=(const FuzzyFindRequest &Req) const { return !(*this == Req); } }; -bool fromJSON(const llvm::json::Value &Value, FuzzyFindRequest &Request); +bool fromJSON(const llvm::json::Value &Value, FuzzyFindRequest &Request, + llvm::json::Path); llvm::json::Value toJSON(const FuzzyFindRequest &Request); struct LookupRequest { diff --git a/clang-tools-extra/clangd/index/Index.cpp b/clang-tools-extra/clangd/index/Index.cpp --- a/clang-tools-extra/clangd/index/Index.cpp +++ b/clang-tools-extra/clangd/index/Index.cpp @@ -31,8 +31,9 @@ return Index; } -bool fromJSON(const llvm::json::Value &Parameters, FuzzyFindRequest &Request) { - llvm::json::ObjectMapper O(Parameters); +bool fromJSON(const llvm::json::Value &Parameters, FuzzyFindRequest &Request, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Parameters, P); int64_t Limit; bool OK = O && O.map("Query", Request.Query) && O.map("Scopes", Request.Scopes) && diff --git a/lldb/source/Utility/StructuredData.cpp b/lldb/source/Utility/StructuredData.cpp --- a/lldb/source/Utility/StructuredData.cpp +++ b/lldb/source/Utility/StructuredData.cpp @@ -57,21 +57,17 @@ if (json::Array *A = value.getAsArray()) return ParseJSONArray(A); - std::string s; - if (json::fromJSON(value, s)) - return std::make_shared(s); + if (auto s = value.getAsString()) + return std::make_shared(*s); - bool b; - if (json::fromJSON(value, b)) - return std::make_shared(b); + if (auto b = value.getAsBoolean()) + return std::make_shared(*b); - int64_t i; - if (json::fromJSON(value, i)) - return std::make_shared(i); + if (auto i = value.getAsInteger(i)) + return std::make_shared(*i); - double d; - if (json::fromJSON(value, d)) - return std::make_shared(d); + if (auto d = value.getAsNumber()) + return std::make_shared(*d); return StructuredData::ObjectSP(); } diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h --- a/llvm/include/llvm/Support/JSON.h +++ b/llvm/include/llvm/Support/JSON.h @@ -253,7 +253,14 @@ /// === Converting JSON values to C++ types === /// /// The convention is to have a deserializer function findable via ADL: -/// fromJSON(const json::Value&, T&)->bool +/// fromJSON(const json::Value&, T&, Path) -> bool +/// +/// The return value indicates overall success, and Path is used for precise +/// error reporting. (The Path::Root passed in at the top level fromJSON call +/// captures any nested error and can render it in context). +/// If conversion fails, fromJSON calls Path::report() and immediately returns. +/// This ensures that the first fatal error survives. +/// /// Deserializers are provided for: /// - bool /// - int and int64_t @@ -557,81 +564,169 @@ return M.erase(ObjectKey(K)); } +/// A "cursor" marking a position within a Value. +/// The Value is a tree, and this is the path from the root to the current node. +/// This is used to associate errors with particular subobjects. +class Path { +public: + class Root; + + /// Records that the value at the current path is invalid. + /// Message is e.g. "expected number" and becomes part of the final error. + /// This overwrites any previously written error message in the root. + void report(llvm::StringLiteral Message); + + /// The root may be treated as a Path. + Path(Root &R) : Parent(nullptr), Seg(&R) {} + /// Derives a path for an array element: this[Index] + Path index(unsigned Index) const { return Path(this, Segment(Index)); } + /// Derives a path for an object field: this.Field + Path field(StringRef Field) const { return Path(this, Segment(Field)); } + +private: + /// One element in a JSON path: an object field (.foo) or array index [27]. + /// Exception: the root Path encodes a pointer to the Path::Root. + class Segment { + uintptr_t Pointer; + unsigned Offset; + + public: + Segment() = default; + Segment(Root *R) : Pointer(reinterpret_cast(R)) {} + Segment(llvm::StringRef Field) + : Pointer(reinterpret_cast(Field.data())), + Offset(static_cast(Field.size())) {} + Segment(unsigned Index) : Pointer(0), Offset(Index) {} + + bool isField() const { return Pointer != 0; } + StringRef field() const { + return StringRef(reinterpret_cast(Pointer), Offset); + } + unsigned index() const { return Offset; } + Root *root() const { return reinterpret_cast(Pointer); } + }; + + const Path *Parent; + Segment Seg; + + Path(const Path *Parent, Segment S) : Parent(Parent), Seg(S) {} +}; + +/// The root is the trivial Path to the root value. +/// It also stores the latest reported error and the path where it occurred. +class Path::Root { + llvm::StringRef Name; + llvm::StringLiteral ErrorMessage; + std::vector ErrorPath; // Only valid in error state. Reversed. + + friend void Path::report(llvm::StringLiteral Message); + +public: + Root(llvm::StringRef Name = "") : Name(Name), ErrorMessage("") {} + // No copy/move allowed as there are incoming pointers. + Root(Root &&) = delete; + Root &operator=(Root &&) = delete; + Root(const Root &) = delete; + Root &operator=(const Root &) = delete; + + /// Returns the last error reported, or else a generic error. + Error getError() const; + /// Print the root value with the error shown inline as a comment. + /// Unrelated parts of the value are elided for brevity, e.g. + /// { + /// "id": 42, + /// "name": /* expected string */ null, + /// "properties": { ... } + /// } + void printErrorContext(const Value &, llvm::raw_ostream &) const; +}; + // Standard deserializers are provided for primitive types. // See comments on Value. -inline bool fromJSON(const Value &E, std::string &Out) { +inline bool fromJSON(const Value &E, std::string &Out, Path P) { if (auto S = E.getAsString()) { Out = std::string(*S); return true; } + P.report("expected string"); return false; } -inline bool fromJSON(const Value &E, int &Out) { +inline bool fromJSON(const Value &E, int &Out, Path P) { if (auto S = E.getAsInteger()) { Out = *S; return true; } + P.report("expected integer"); return false; } -inline bool fromJSON(const Value &E, int64_t &Out) { +inline bool fromJSON(const Value &E, int64_t &Out, Path P) { if (auto S = E.getAsInteger()) { Out = *S; return true; } + P.report("expected integer"); return false; } -inline bool fromJSON(const Value &E, double &Out) { +inline bool fromJSON(const Value &E, double &Out, Path P) { if (auto S = E.getAsNumber()) { Out = *S; return true; } + P.report("expected number"); return false; } -inline bool fromJSON(const Value &E, bool &Out) { +inline bool fromJSON(const Value &E, bool &Out, Path P) { if (auto S = E.getAsBoolean()) { Out = *S; return true; } + P.report("expected boolean"); return false; } -inline bool fromJSON(const Value &E, std::nullptr_t &Out) { +inline bool fromJSON(const Value &E, std::nullptr_t &Out, Path P) { if (auto S = E.getAsNull()) { Out = *S; return true; } + P.report("expected null"); return false; } -template bool fromJSON(const Value &E, llvm::Optional &Out) { +template +bool fromJSON(const Value &E, llvm::Optional &Out, Path P) { if (E.getAsNull()) { Out = llvm::None; return true; } T Result; - if (!fromJSON(E, Result)) + if (!fromJSON(E, Result, P)) return false; Out = std::move(Result); return true; } -template bool fromJSON(const Value &E, std::vector &Out) { +template +bool fromJSON(const Value &E, std::vector &Out, Path P) { if (auto *A = E.getAsArray()) { Out.clear(); Out.resize(A->size()); for (size_t I = 0; I < A->size(); ++I) - if (!fromJSON((*A)[I], Out[I])) + if (!fromJSON((*A)[I], Out[I], P.index(I))) return false; return true; } + P.report("expected array"); return false; } template -bool fromJSON(const Value &E, std::map &Out) { +bool fromJSON(const Value &E, std::map &Out, Path P) { if (auto *O = E.getAsObject()) { Out.clear(); for (const auto &KV : *O) - if (!fromJSON(KV.second, Out[std::string(llvm::StringRef(KV.first))])) + if (!fromJSON(KV.second, Out[std::string(llvm::StringRef(KV.first))], + P.field(KV.first))) return false; return true; } + P.report("expected object"); return false; } @@ -644,42 +739,50 @@ /// /// Example: /// \code -/// bool fromJSON(const Value &E, MyStruct &R) { -/// ObjectMapper O(E); +/// bool fromJSON(const Value &E, MyStruct &R, Path P) { +/// ObjectMapper O(E, P); /// if (!O || !O.map("mandatory_field", R.MandatoryField)) -/// return false; +/// return false; // error details are already reported /// O.map("optional_field", R.OptionalField); /// return true; /// } /// \endcode class ObjectMapper { public: - ObjectMapper(const Value &E) : O(E.getAsObject()) {} + /// If O is not an object, this mapper is invalid and an error is reported. + ObjectMapper(const Value &E, Path P) : O(E.getAsObject()), P(P) { + if (!O) + P.report("expected object"); + } /// True if the expression is an object. /// Must be checked before calling map(). - operator bool() { return O; } + operator bool() const { return O; } - /// Maps a property to a field, if it exists. - template bool map(StringRef Prop, T &Out) { + /// Maps a property to a field. + /// If the property is missing or invalid, reports an error. + template bool map(StringLiteral Prop, T &Out) { assert(*this && "Must check this is an object before calling map()"); if (const Value *E = O->get(Prop)) - return fromJSON(*E, Out); + return fromJSON(*E, Out, P.field(Prop)); + P.field(Prop).report("missing value"); return false; } /// Maps a property to a field, if it exists. + /// If the property exists and is invalid, reports an error. /// (Optional requires special handling, because missing keys are OK). - template bool map(StringRef Prop, llvm::Optional &Out) { + template bool map(StringLiteral Prop, llvm::Optional &Out) { assert(*this && "Must check this is an object before calling map()"); if (const Value *E = O->get(Prop)) - return fromJSON(*E, Out); + return fromJSON(*E, Out, P.field(Prop)); Out = llvm::None; return true; } private: const Object *O; + Path P; }; /// Parses the provided JSON source, or returns a ParseError. @@ -703,9 +806,24 @@ } }; +/// Version of parse() that converts the parsed value to the type T. +/// RootName describes the root object and is used in error messages. +template +Expected parse(const llvm::StringRef &JSON, const char *RootName = "") { + auto V = parse(JSON); + if (!V) + return V.takeError(); + Path::Root R(RootName); + T Result; + if (fromJSON(*V, Result, R)) + return std::move(Result); + return R.getError(); +} + /// json::OStream allows writing well-formed JSON without materializing /// all structures as json::Value ahead of time. /// It's faster, lower-level, and less safe than OS << json::Value. +/// It also allows emitting more constructs, such as comments. /// /// Only one "top-level" object can be written to a stream. /// Simplest usage involves passing lambdas (Blocks) to fill in containers: @@ -791,6 +909,10 @@ Contents(); objectEnd(); } + /// Emit a JavaScript comment associated with the next printed value. + /// The string must be valid until the next attribute or value is emitted. + /// Comments are not part of standard JSON, and many parsers reject them! + void comment(llvm::StringRef); // High level functions to output object attributes. // Valid only within an object (any number of times). @@ -826,6 +948,7 @@ } void valueBegin(); + void flushComment(); void newline(); enum Context { @@ -838,6 +961,7 @@ bool HasValue = false; }; llvm::SmallVector Stack; // Never empty. + llvm::StringRef PendingComment; llvm::raw_ostream &OS; unsigned IndentSize; unsigned Indent = 0; diff --git a/llvm/lib/Analysis/TFUtils.cpp b/llvm/lib/Analysis/TFUtils.cpp --- a/llvm/lib/Analysis/TFUtils.cpp +++ b/llvm/lib/Analysis/TFUtils.cpp @@ -104,7 +104,9 @@ Ctx.emitError("Unable to parse JSON Value as spec (" + Message + "): " + S); return None; }; - json::ObjectMapper Mapper(Value); + // FIXME: accept a Path as a parameter, and use it for error reporting. + json::Path::Root Root("tensor_spec"); + json::ObjectMapper Mapper(Value, Root); if (!Mapper) return EmitError("Value is not a dict"); diff --git a/llvm/lib/Support/JSON.cpp b/llvm/lib/Support/JSON.cpp --- a/llvm/lib/Support/JSON.cpp +++ b/llvm/lib/Support/JSON.cpp @@ -7,8 +7,11 @@ //===---------------------------------------------------------------------===// #include "llvm/Support/JSON.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" #include namespace llvm { @@ -198,6 +201,167 @@ llvm_unreachable("Unknown value kind"); } +void Path::report(llvm::StringLiteral Msg) { + // Walk up to the root context, and count the number of segments. + unsigned Count = 0; + const Path *P; + for (P = this; P->Parent != nullptr; P = P->Parent) + ++Count; + Path::Root *R = P->Seg.root(); + // Fill in the error message and copy the path (in reverse order). + R->ErrorMessage = Msg; + R->ErrorPath.resize(Count); + auto It = R->ErrorPath.begin(); + for (P = this; P->Parent != nullptr; P = P->Parent) + *It++ = P->Seg; +} + +Error Path::Root::getError() const { + std::string S; + raw_string_ostream OS(S); + OS << (ErrorMessage.empty() ? "invalid JSON contents" : ErrorMessage); + if (ErrorPath.empty()) { + if (!Name.empty()) + OS << " when parsing " << Name; + } else { + OS << " at " << (Name.empty() ? "(root)" : Name); + for (const Path::Segment &S : llvm::reverse(ErrorPath)) { + if (S.isField()) + OS << '.' << S.field(); + else + OS << '[' << S.index() << ']'; + } + } + return createStringError(llvm::inconvertibleErrorCode(), OS.str()); +} + +namespace { + +std::vector sortedElements(const Object &O) { + std::vector Elements; + for (const auto &E : O) + Elements.push_back(&E); + llvm::sort(Elements, + [](const Object::value_type *L, const Object::value_type *R) { + return L->first < R->first; + }); + return Elements; +} + +// Prints a one-line version of a value that isn't our main focus. +// We interleave writes to OS and JOS, exploiting the lack of extra buffering. +// This is OK as we own the implementation. +// FIXME: once we have a "write custom serialized value" API, use it here. +void abbreviate(const Value &V, OStream &JOS, raw_ostream &OS) { + switch (V.kind()) { + case Value::Array: + JOS.array([&] { + if (!V.getAsArray()->empty()) + OS << " ... "; + }); + break; + case Value::Object: + JOS.object([&] { + if (!V.getAsObject()->empty()) + OS << " ... "; + }); + break; + case Value::String: { + llvm::StringRef S = *V.getAsString(); + if (S.size() < 40) { + JOS.value(V); + } else { + std::string Truncated = fixUTF8(S.take_front(37)); + Truncated.append("..."); + JOS.value(Truncated); + } + break; + } + default: + JOS.value(V); + } +} + +// Prints a semi-expanded version of a value that is our main focus. +// Array/Object entries are printed, but not recursively as they may be huge. +void abbreviateChildren(const Value &V, OStream &JOS, raw_ostream &OS) { + switch (V.kind()) { + case Value::Array: + JOS.array([&] { + for (const auto &V : *V.getAsArray()) + abbreviate(V, JOS, OS); + }); + break; + case Value::Object: + JOS.object([&] { + for (const auto *KV : sortedElements(*V.getAsObject())) { + JOS.attributeBegin(KV->first); + abbreviate(KV->second, JOS, OS); + JOS.attributeEnd(); + } + }); + break; + default: + JOS.value(V); + } +} + +} // namespace + +void Path::Root::printErrorContext(const Value &R, raw_ostream &OS) const { + OStream JOS(OS, /*IndentSize=*/2); + // PrintValue recurses down the path, printing the ancestors of our target. + // Siblings of nodes along the path are printed with abbreviate(), and the + // target itself is printed with the somewhat richer abbreviateChildren(). + // 'Recurse' is the lambda itself, to allow recursive calls. + auto PrintValue = [&](const Value &V, ArrayRef Path, auto &Recurse) { + // Print the target node itself, with the error as a comment. + // Also used if we can't follow our path, e.g. it names a field that + // *should* exist but doesn't. + auto HighlightCurrent = [&] { + std::string Comment = "error: "; + Comment.append(ErrorMessage.data(), ErrorMessage.size()); + JOS.comment(Comment); + abbreviateChildren(V, JOS, OS); + }; + if (Path.empty()) // We reached our target. + return HighlightCurrent(); + const Segment &S = Path.back(); // Path is in reverse order. + if (S.isField()) { + // Current node is an object, path names a field. + llvm::StringRef FieldName = S.field(); + const Object *O = V.getAsObject(); + if (!O || !O->get(FieldName)) + return HighlightCurrent(); + JOS.object([&] { + for (const auto *KV : sortedElements(*O)) { + JOS.attributeBegin(KV->first); + if (FieldName.equals(KV->first)) + Recurse(KV->second, Path.drop_back(), Recurse); + else + abbreviate(KV->second, JOS, OS); + JOS.attributeEnd(); + } + }); + } else { + // Current node is an array, path names an element. + const Array *A = V.getAsArray(); + if (!A || S.index() >= A->size()) + return HighlightCurrent(); + JOS.array([&] { + unsigned Current = 0; + for (const auto &V : *A) { + if (Current++ == S.index()) + Recurse(V, Path.drop_back(), Recurse); + else + abbreviate(V, JOS, OS); + } + }); + } + }; + PrintValue(R, ErrorPath, PrintValue); +} + namespace { // Simple recursive-descent JSON parser. class Parser { @@ -518,17 +682,6 @@ } char ParseError::ID = 0; -static std::vector sortedElements(const Object &O) { - std::vector Elements; - for (const auto &E : O) - Elements.push_back(&E); - llvm::sort(Elements, - [](const Object::value_type *L, const Object::value_type *R) { - return L->first < R->first; - }); - return Elements; -} - bool isUTF8(llvm::StringRef S, size_t *ErrOffset) { // Fast-path for ASCII, which is valid UTF-8. if (LLVM_LIKELY(isASCII(S))) @@ -633,9 +786,40 @@ } if (Stack.back().Ctx == Array) newline(); + flushComment(); Stack.back().HasValue = true; } +void OStream::comment(llvm::StringRef Comment) { + assert(PendingComment.empty() && "Only one comment per value!"); + PendingComment = Comment; +} + +void OStream::flushComment() { + if (PendingComment.empty()) + return; + OS << (IndentSize ? "/* " : "/*"); + // Be sure not to accidentally emit "*/". Transform to "* /". + while (!PendingComment.empty()) { + auto Pos = PendingComment.find("*/"); + if (Pos == StringRef::npos) { + OS << PendingComment; + PendingComment = ""; + } else { + OS << PendingComment.take_front(Pos) << "* /"; + PendingComment = PendingComment.drop_front(Pos + 2); + } + } + OS << (IndentSize ? " */" : "*/"); + // Comments are on their own line unless attached to an attribute value. + if (Stack.size() > 1 && Stack.back().Ctx == Singleton) { + if (IndentSize) + OS << ' '; + } else { + newline(); + } +} + void llvm::json::OStream::newline() { if (IndentSize) { OS.write('\n'); @@ -657,6 +841,7 @@ if (Stack.back().HasValue) newline(); OS << ']'; + assert(PendingComment.empty()); Stack.pop_back(); assert(!Stack.empty()); } @@ -675,6 +860,7 @@ if (Stack.back().HasValue) newline(); OS << '}'; + assert(PendingComment.empty()); Stack.pop_back(); assert(!Stack.empty()); } @@ -684,6 +870,7 @@ if (Stack.back().HasValue) OS << ','; newline(); + flushComment(); Stack.back().HasValue = true; Stack.emplace_back(); Stack.back().Ctx = Singleton; @@ -701,6 +888,7 @@ void llvm::json::OStream::attributeEnd() { assert(Stack.back().Ctx == Singleton); assert(Stack.back().HasValue && "Attribute must have a value"); + assert(PendingComment.empty()); Stack.pop_back(); assert(Stack.back().Ctx == Object); } diff --git a/llvm/unittests/Support/JSONTest.cpp b/llvm/unittests/Support/JSONTest.cpp --- a/llvm/unittests/Support/JSONTest.cpp +++ b/llvm/unittests/Support/JSONTest.cpp @@ -8,6 +8,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -372,14 +373,21 @@ return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None") << ", " << S.B << ")"; } -bool fromJSON(const Value &E, CustomStruct &R) { - ObjectMapper O(E); +bool fromJSON(const Value &E, CustomStruct &R, Path P) { + ObjectMapper O(E, P); if (!O || !O.map("str", R.S) || !O.map("int", R.I)) return false; O.map("bool", R.B); return true; } +static std::string errorContext(const Value &V, const Path::Root &R) { + std::string Context; + llvm::raw_string_ostream OS(Context); + R.printErrorContext(V, OS); + return OS.str(); +} + TEST(JSONTest, Deserialize) { std::map> R; CustomStruct ExpectedStruct = {"foo", 42, true}; @@ -403,16 +411,58 @@ CustomStruct("bar", llvm::None, false), CustomStruct("baz", llvm::None, false), }; - ASSERT_TRUE(fromJSON(J, R)); + Path::Root Root("CustomStruct"); + ASSERT_TRUE(fromJSON(J, R, Root)); EXPECT_EQ(R, Expected); + (*J.getAsObject()->getArray("foo"))[0] = 123; + ASSERT_FALSE(fromJSON(J, R, Root)); + EXPECT_EQ("expected object at CustomStruct.foo[0]", + toString(Root.getError())); + const char *ExpectedDump = R"({ + "foo": [ + /* error: expected object */ + 123, + { ... }, + { ... } + ] +})"; + EXPECT_EQ(ExpectedDump, errorContext(J, Root)); + CustomStruct V; - EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V; - EXPECT_FALSE(fromJSON(Object{}, V)) << "Missing required field " << V; - EXPECT_FALSE(fromJSON(Object{{"str", 1}}, V)) << "Wrong type " << V; + EXPECT_FALSE(fromJSON(nullptr, V, Root)); + EXPECT_EQ("expected object when parsing CustomStruct", + toString(Root.getError())); + + EXPECT_FALSE(fromJSON(Object{}, V, Root)); + EXPECT_EQ("missing value at CustomStruct.str", toString(Root.getError())); + + EXPECT_FALSE(fromJSON(Object{{"str", 1}}, V, Root)); + EXPECT_EQ("expected string at CustomStruct.str", toString(Root.getError())); + // Optional must parse as the correct type if present. - EXPECT_FALSE(fromJSON(Object{{"str", 1}, {"int", "string"}}, V)) - << "Wrong type for Optional " << V; + EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root)); + EXPECT_EQ("expected integer at CustomStruct.int", toString(Root.getError())); +} + +TEST(JSONTest, ParseDeserialize) { + auto E = parse>(R"json( + [{"str": "foo", "int": 42}, {"int": 42}] + )json"); + EXPECT_THAT_EXPECTED(E, FailedWithMessage("missing value at (root)[1].str")); + + E = parse>(R"json( + [{"str": "foo", "int": 42}, {"str": "bar"} + )json"); + EXPECT_THAT_EXPECTED( + E, + FailedWithMessage("[3:2, byte=50]: Expected , or ] after array element")); + + E = parse>(R"json( + [{"str": "foo", "int": 42}] + )json"); + EXPECT_THAT_EXPECTED(E, Succeeded()); + EXPECT_THAT(*E, testing::SizeIs(1)); } TEST(JSONTest, Stream) { @@ -420,15 +470,19 @@ std::string S; llvm::raw_string_ostream OS(S); OStream J(OS, Indent); + J.comment("top*/level"); J.object([&] { J.attributeArray("foo", [&] { J.value(nullptr); + J.comment("element"); J.value(42.5); J.arrayBegin(); J.value(43); J.arrayEnd(); }); + J.comment("attribute"); J.attributeBegin("bar"); + J.comment("attribute value"); J.objectBegin(); J.objectEnd(); J.attributeEnd(); @@ -437,22 +491,60 @@ return OS.str(); }; - const char *Plain = R"({"foo":[null,42.5,[43]],"bar":{},"baz":"xyz"})"; + const char *Plain = + R"(/*top* /level*/{"foo":[null,/*element*/42.5,[43]],/*attribute*/"bar":/*attribute value*/{},"baz":"xyz"})"; EXPECT_EQ(Plain, StreamStuff(0)); - const char *Pretty = R"({ + const char *Pretty = R"(/* top* /level */ +{ "foo": [ null, + /* element */ 42.5, [ 43 ] ], - "bar": {}, + /* attribute */ + "bar": /* attribute value */ {}, "baz": "xyz" })"; EXPECT_EQ(Pretty, StreamStuff(2)); } +TEST(JSONTest, Path) { + Path::Root R("foo"); + Path P = R, A = P.field("a"), B = P.field("b"); + P.report("oh no"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("oh no when parsing foo")); + A.index(1).field("c").index(2).report("boom"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("boom at foo.a[1].c[2]")); + B.field("d").field("e").report("bam"); + EXPECT_THAT_ERROR(R.getError(), FailedWithMessage("bam at foo.b.d.e")); + + Value V = Object{ + {"a", Array{42}}, + {"b", + Object{{"d", + Object{ + {"e", Array{1, Object{{"x", "y"}}}}, + {"f", "a moderately long string: 48 characters in total"}, + }}}}, + }; + const char *Expected = R"({ + "a": [ ... ], + "b": { + "d": { + "e": /* error: bam */ [ + 1, + { ... } + ], + "f": "a moderately long string: 48 characte..." + } + } +})"; + EXPECT_EQ(Expected, errorContext(V, R)); +} + } // namespace } // namespace json } // namespace llvm