authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-05-10 05:14:07-04:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2024-05-29 08:22:01-04:00
log8559d5dbd6f14916a1fa757a3a75a987bf697868
treee135b79bbc23c7eeb295c4c765f7058afe6d642d
parent925e17879b851cb44d1a419eb08a2cb31d9e5eb7

fancy type errors


2 files changed, 90 insertions(+), 25 deletions(-)

lib/std/json/scanner.zig+4-4
......@@ -227,7 +227,7 @@ pub const Diagnostics = struct {
227227 /// file_name if non-null will be printed in a line with the line and column numbers;
228228 /// it is purely aesthetic and is not touched on any actual file system.
229229 pub fn dump(self: *const @This(), writer: anytype, err: anyerror, file_name: ?[]const u8) !void {
230 try writer.print("{s}:{}:{}: {s}\n", .{file_name orelse "<json>", self.getLine(), self.getColumn(), @errorName(err)});
230 try writer.print("{s}:{}:{}: {s}\n", .{ file_name orelse "<json>", self.getLine(), self.getColumn(), @errorName(err) });
231231
232232 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.
233233 // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.)
......@@ -258,7 +258,7 @@ pub const Diagnostics = struct {
258258 }
259259 end += 1;
260260 }
261 try writer.print("{s}{s}{s}\n", .{start_elipsis, self.current_input[start..end], end_elipsis});
261 try writer.print("{s}{s}{s}\n", .{ start_elipsis, self.current_input[start..end], end_elipsis });
262262 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);
263263 try writer.writeAll("^\n");
264264
......@@ -268,9 +268,9 @@ pub const Diagnostics = struct {
268268 }
269269};
270270
271pub inline fn maybeRecordDiagnosticContext(allocator: Allocator, maybe_diagnostics: ?*Diagnostics, context: []const u8) Allocator.Error!void {
271pub inline fn maybeRecordDiagnosticContext(allocator: Allocator, maybe_diagnostics: ?*Diagnostics, context: []const u8) void {
272272 if (maybe_diagnostics) |diag| {
273 try diag.recordContext(allocator, context);
273 diag.recordContext(allocator, context) catch {};
274274 }
275275}
276276
lib/std/json/static.zig+86-21
......@@ -6,6 +6,7 @@ const ArrayList = std.ArrayList;
66
77const Scanner = @import("./scanner.zig").Scanner;
88const Token = @import("./scanner.zig").Token;
9const TokenType = @import("./scanner.zig").TokenType;
910const AllocWhen = @import("./scanner.zig").AllocWhen;
1011const Diagnostics = @import("./scanner.zig").Diagnostics;
1112const default_max_value_len = @import("./scanner.zig").default_max_value_len;
......@@ -219,13 +220,13 @@ pub fn innerParse(
219220 options: ParseOptions,
220221) ParseError(@TypeOf(source.*))!T {
221222 errdefer source.saveDiagnostics();
222 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T)) catch {};
223 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T));
223224 switch (@typeInfo(T)) {
224225 .Bool => {
225226 return switch (try source.next()) {
226227 .true => true,
227228 .false => false,
228 else => error.UnexpectedToken,
229 else => |t| return typeError(allocator, options.diagnostics, t, "bool"),
229230 };
230231 },
231232 .Float, .ComptimeFloat => {
......@@ -233,7 +234,7 @@ pub fn innerParse(
233234 defer freeAllocated(allocator, token);
234235 const slice = switch (token) {
235236 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
236 else => return error.UnexpectedToken,
237 else => |t| return typeError(allocator, options.diagnostics, t, "float"),
237238 };
238239 return try std.fmt.parseFloat(T, slice);
239240 },
......@@ -242,7 +243,7 @@ pub fn innerParse(
242243 defer freeAllocated(allocator, token);
243244 const slice = switch (token) {
244245 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
245 else => return error.UnexpectedToken,
246 else => |t| return typeError(allocator, options.diagnostics, t, "int"),
246247 };
247248 return sliceToInt(T, slice);
248249 },
......@@ -266,7 +267,7 @@ pub fn innerParse(
266267 defer freeAllocated(allocator, token);
267268 const slice = switch (token) {
268269 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
269 else => return error.UnexpectedToken,
270 else => |t| return typeError(allocator, options.diagnostics, t, "enum (number or string)"),
270271 };
271272 return sliceToEnum(T, slice);
272273 },
......@@ -277,15 +278,16 @@ pub fn innerParse(
277278
278279 if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
279280
280 if (.object_begin != try source.next()) return error.UnexpectedToken;
281 switch (try source.next()) {
282 .object_begin => {},
283 else => |t| return typeError(allocator, options.diagnostics, t, "union (object with one field)"),
284 }
281285
282286 var result: ?T = null;
283287 var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
284288 const field_name = switch (name_token.?) {
285289 inline .string, .allocated_string => |slice| slice,
286 else => {
287 return error.UnexpectedToken;
288 },
290 else => return error.MissingField,
289291 };
290292
291293 inline for (unionInfo.fields) |u_field| {
......@@ -296,7 +298,10 @@ pub fn innerParse(
296298 name_token = null;
297299 if (u_field.type == void) {
298300 // void isn't really a json type, but we can support void payload union tags with {} as a value.
299 if (.object_begin != try source.next()) return error.UnexpectedToken;
301 switch (try source.next()) {
302 .object_begin => {},
303 else => |t| return typeError(allocator, options.diagnostics, t, "void payload ('{}')"),
304 }
300305 if (.object_end != try source.next()) return error.UnknownField;
301306 result = @unionInit(T, u_field.name, {});
302307 } else {
......@@ -310,21 +315,25 @@ pub fn innerParse(
310315 return error.UnknownField;
311316 }
312317
313 if (.object_end != try source.next()) return error.UnexpectedToken;
318 if (.object_end != try source.next()) return error.UnknownField;
314319
315320 return result.?;
316321 },
317322
318323 .Struct => |structInfo| {
319324 if (structInfo.is_tuple) {
320 if (.array_begin != try source.next()) return error.UnexpectedToken;
325 switch (try source.next()) {
326 .array_begin => {},
327 else => |t| return typeError(allocator, options.diagnostics, t, "tuple (array of values)"),
328 }
321329
322330 var r: T = undefined;
323331 inline for (0..structInfo.fields.len) |i| {
332 if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch;
324333 r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options);
325334 }
326335
327 if (.array_end != try source.next()) return error.UnexpectedToken;
336 if (.array_end != try source.next()) return error.LengthMismatch;
328337
329338 return r;
330339 }
......@@ -333,7 +342,10 @@ pub fn innerParse(
333342 return T.jsonParse(allocator, source, options);
334343 }
335344
336 if (.object_begin != try source.next()) return error.UnexpectedToken;
345 switch (try source.next()) {
346 .object_begin => {},
347 else => |t| return typeError(allocator, options.diagnostics, t, "struct ('{...}')"),
348 }
337349
338350 var r: T = undefined;
339351 var fields_seen = [_]bool{false} ** structInfo.fields.len;
......@@ -354,7 +366,7 @@ pub fn innerParse(
354366 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
355367 // (Recursing into innerParse() might trigger more allocations.)
356368 freeAllocated(allocator, name_token.?);
357 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name) catch {};
369 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T) ++ "." ++ field.name);
358370 name_token = null;
359371 if (fields_seen[i]) {
360372 switch (options.duplicate_field_behavior) {
......@@ -393,7 +405,7 @@ pub fn innerParse(
393405 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
394406 },
395407 .string => {
396 if (arrayInfo.child != u8) return error.UnexpectedToken;
408 if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
397409 // Fixed-length string.
398410
399411 var r: T = undefined;
......@@ -437,7 +449,7 @@ pub fn innerParse(
437449 return r;
438450 },
439451
440 else => return error.UnexpectedToken,
452 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
441453 }
442454 },
443455
......@@ -446,7 +458,7 @@ pub fn innerParse(
446458 .array_begin => {
447459 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
448460 },
449 else => return error.UnexpectedToken,
461 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
450462 }
451463 },
452464
......@@ -485,7 +497,7 @@ pub fn innerParse(
485497 return try arraylist.toOwnedSlice();
486498 },
487499 .string => {
488 if (ptrInfo.child != u8) return error.UnexpectedToken;
500 if (ptrInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
489501
490502 // Dynamic length string.
491503 if (ptrInfo.sentinel) |sentinel_ptr| {
......@@ -507,7 +519,7 @@ pub fn innerParse(
507519 }
508520 }
509521 },
510 else => return error.UnexpectedToken,
522 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
511523 }
512524 },
513525 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
......@@ -531,14 +543,67 @@ fn internalParseArray(
531543 var r: T = undefined;
532544 var i: usize = 0;
533545 while (i < len) : (i += 1) {
546 if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch;
534547 r[i] = try innerParse(Child, allocator, source, options);
535548 }
536549
537 if (.array_end != try source.next()) return error.UnexpectedToken;
550 if (.array_end != try source.next()) return error.LengthMismatch;
538551
539552 return r;
540553}
541554
555fn coerceToTokenType(token: anytype) TokenType {
556 if (@TypeOf(token) == TokenType) return token;
557 return switch (@as(std.meta.Tag(Token), token)) {
558 // Coerce Token tag into TokenType
559 .object_begin => .object_begin,
560 .array_begin => .array_begin,
561
562 .true => .true,
563 .false => .false,
564 .null => .null,
565
566 .number,
567 .partial_number,
568 .allocated_number,
569 => .number,
570
571 .string,
572 .partial_string,
573 .partial_string_escaped_1,
574 .partial_string_escaped_2,
575 .partial_string_escaped_3,
576 .partial_string_escaped_4,
577 .allocated_string,
578 => .string,
579
580 .object_end => .object_end,
581 .array_end => .array_end,
582 .end_of_document => .end_of_document,
583 };
584}
585fn typeError(allocator: Allocator, diagnostics: ?*Diagnostics, token: anytype, expected: []const u8) error{UnexpectedToken} {
586 if (diagnostics) |diag| {
587 if (std.fmt.allocPrint(allocator, "expected: {s}, found: {s}", .{
588 expected,
589 switch (coerceToTokenType(token)) {
590 .object_begin => "'{'",
591 .array_begin => "'['",
592 .true, .false => "bool",
593 .null => "null",
594 .number => "number",
595 .string => "string",
596 .object_end => unreachable, // type errors happen at the start of a value.
597 .array_end => unreachable, // type errors happen at the start of a value.
598 .end_of_document => unreachable, // type errors happen at the start of a value.
599 },
600 })) |s| {
601 diag.recordContext(allocator, s) catch {};
602 } else |_| {}
603 }
604 return error.UnexpectedToken;
605}
606
542607/// This is an internal function called recursively
543608/// during the implementation of `parseFromValueLeaky`.
544609/// It is exposed primarily to enable custom `jsonParseFromValue()` methods to call back into the `parseFromValue*` system,