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 {...@@ -227,7 +227,7 @@ pub const Diagnostics = struct {
227 /// file_name if non-null will be printed in a line with the line and column numbers;227 /// file_name if non-null will be printed in a line with the line and column numbers;
228 /// it is purely aesthetic and is not touched on any actual file system.228 /// it is purely aesthetic and is not touched on any actual file system.
229 pub fn dump(self: *const @This(), writer: anytype, err: anyerror, file_name: ?[]const u8) !void {229 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
232 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.232 // Show a "line" of context, or in case of very long lines, just an excerpt of the line.
233 // (Very long lines are common in minified JSON such as in an HTTP API or other machine-to-machine contexts.)233 // (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 {...@@ -258,7 +258,7 @@ pub const Diagnostics = struct {
258 }258 }
259 end += 1;259 end += 1;
260 }260 }
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 });
262 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);262 try writer.writeByteNTimes(' ', start_elipsis.len + self.cursor_in_current_input - start);
263 try writer.writeAll("^\n");263 try writer.writeAll("^\n");
264264
...@@ -268,9 +268,9 @@ pub const Diagnostics = struct {...@@ -268,9 +268,9 @@ pub const Diagnostics = struct {
268 }268 }
269};269};
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 {
272 if (maybe_diagnostics) |diag| {272 if (maybe_diagnostics) |diag| {
273 try diag.recordContext(allocator, context);273 diag.recordContext(allocator, context) catch {};
274 }274 }
275}275}
276276
lib/std/json/static.zig+86-21
...@@ -6,6 +6,7 @@ const ArrayList = std.ArrayList;...@@ -6,6 +6,7 @@ const ArrayList = std.ArrayList;
66
7const Scanner = @import("./scanner.zig").Scanner;7const Scanner = @import("./scanner.zig").Scanner;
8const Token = @import("./scanner.zig").Token;8const Token = @import("./scanner.zig").Token;
9const TokenType = @import("./scanner.zig").TokenType;
9const AllocWhen = @import("./scanner.zig").AllocWhen;10const AllocWhen = @import("./scanner.zig").AllocWhen;
10const Diagnostics = @import("./scanner.zig").Diagnostics;11const Diagnostics = @import("./scanner.zig").Diagnostics;
11const default_max_value_len = @import("./scanner.zig").default_max_value_len;12const default_max_value_len = @import("./scanner.zig").default_max_value_len;
...@@ -219,13 +220,13 @@ pub fn innerParse(...@@ -219,13 +220,13 @@ pub fn innerParse(
219 options: ParseOptions,220 options: ParseOptions,
220) ParseError(@TypeOf(source.*))!T {221) ParseError(@TypeOf(source.*))!T {
221 errdefer source.saveDiagnostics();222 errdefer source.saveDiagnostics();
222 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T)) catch {};223 errdefer maybeRecordDiagnosticContext(allocator, options.diagnostics, @typeName(T));
223 switch (@typeInfo(T)) {224 switch (@typeInfo(T)) {
224 .Bool => {225 .Bool => {
225 return switch (try source.next()) {226 return switch (try source.next()) {
226 .true => true,227 .true => true,
227 .false => false,228 .false => false,
228 else => error.UnexpectedToken,229 else => |t| return typeError(allocator, options.diagnostics, t, "bool"),
229 };230 };
230 },231 },
231 .Float, .ComptimeFloat => {232 .Float, .ComptimeFloat => {
...@@ -233,7 +234,7 @@ pub fn innerParse(...@@ -233,7 +234,7 @@ pub fn innerParse(
233 defer freeAllocated(allocator, token);234 defer freeAllocated(allocator, token);
234 const slice = switch (token) {235 const slice = switch (token) {
235 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,236 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
236 else => return error.UnexpectedToken,237 else => |t| return typeError(allocator, options.diagnostics, t, "float"),
237 };238 };
238 return try std.fmt.parseFloat(T, slice);239 return try std.fmt.parseFloat(T, slice);
239 },240 },
...@@ -242,7 +243,7 @@ pub fn innerParse(...@@ -242,7 +243,7 @@ pub fn innerParse(
242 defer freeAllocated(allocator, token);243 defer freeAllocated(allocator, token);
243 const slice = switch (token) {244 const slice = switch (token) {
244 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,245 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,
245 else => return error.UnexpectedToken,246 else => |t| return typeError(allocator, options.diagnostics, t, "int"),
246 };247 };
247 return sliceToInt(T, slice);248 return sliceToInt(T, slice);
248 },249 },
...@@ -266,7 +267,7 @@ pub fn innerParse(...@@ -266,7 +267,7 @@ pub fn innerParse(
266 defer freeAllocated(allocator, token);267 defer freeAllocated(allocator, token);
267 const slice = switch (token) {268 const slice = switch (token) {
268 inline .number, .allocated_number, .string, .allocated_string => |slice| slice,269 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)"),
270 };271 };
271 return sliceToEnum(T, slice);272 return sliceToEnum(T, slice);
272 },273 },
...@@ -277,15 +278,16 @@ pub fn innerParse(...@@ -277,15 +278,16 @@ pub fn innerParse(
277278
278 if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");279 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
282 var result: ?T = null;286 var result: ?T = null;
283 var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);287 var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?);
284 const field_name = switch (name_token.?) {288 const field_name = switch (name_token.?) {
285 inline .string, .allocated_string => |slice| slice,289 inline .string, .allocated_string => |slice| slice,
286 else => {290 else => return error.MissingField,
287 return error.UnexpectedToken;
288 },
289 };291 };
290292
291 inline for (unionInfo.fields) |u_field| {293 inline for (unionInfo.fields) |u_field| {
...@@ -296,7 +298,10 @@ pub fn innerParse(...@@ -296,7 +298,10 @@ pub fn innerParse(
296 name_token = null;298 name_token = null;
297 if (u_field.type == void) {299 if (u_field.type == void) {
298 // void isn't really a json type, but we can support void payload union tags with {} as a value.300 // 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 }
300 if (.object_end != try source.next()) return error.UnknownField;305 if (.object_end != try source.next()) return error.UnknownField;
301 result = @unionInit(T, u_field.name, {});306 result = @unionInit(T, u_field.name, {});
302 } else {307 } else {
...@@ -310,21 +315,25 @@ pub fn innerParse(...@@ -310,21 +315,25 @@ pub fn innerParse(
310 return error.UnknownField;315 return error.UnknownField;
311 }316 }
312317
313 if (.object_end != try source.next()) return error.UnexpectedToken;318 if (.object_end != try source.next()) return error.UnknownField;
314319
315 return result.?;320 return result.?;
316 },321 },
317322
318 .Struct => |structInfo| {323 .Struct => |structInfo| {
319 if (structInfo.is_tuple) {324 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
322 var r: T = undefined;330 var r: T = undefined;
323 inline for (0..structInfo.fields.len) |i| {331 inline for (0..structInfo.fields.len) |i| {
332 if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch;
324 r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options);333 r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options);
325 }334 }
326335
327 if (.array_end != try source.next()) return error.UnexpectedToken;336 if (.array_end != try source.next()) return error.LengthMismatch;
328337
329 return r;338 return r;
330 }339 }
...@@ -333,7 +342,10 @@ pub fn innerParse(...@@ -333,7 +342,10 @@ pub fn innerParse(
333 return T.jsonParse(allocator, source, options);342 return T.jsonParse(allocator, source, options);
334 }343 }
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
338 var r: T = undefined;350 var r: T = undefined;
339 var fields_seen = [_]bool{false} ** structInfo.fields.len;351 var fields_seen = [_]bool{false} ** structInfo.fields.len;
...@@ -354,7 +366,7 @@ pub fn innerParse(...@@ -354,7 +366,7 @@ pub fn innerParse(
354 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.366 // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object.
355 // (Recursing into innerParse() might trigger more allocations.)367 // (Recursing into innerParse() might trigger more allocations.)
356 freeAllocated(allocator, name_token.?);368 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);
358 name_token = null;370 name_token = null;
359 if (fields_seen[i]) {371 if (fields_seen[i]) {
360 switch (options.duplicate_field_behavior) {372 switch (options.duplicate_field_behavior) {
...@@ -393,7 +405,7 @@ pub fn innerParse(...@@ -393,7 +405,7 @@ pub fn innerParse(
393 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);405 return internalParseArray(T, arrayInfo.child, arrayInfo.len, allocator, source, options);
394 },406 },
395 .string => {407 .string => {
396 if (arrayInfo.child != u8) return error.UnexpectedToken;408 if (arrayInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
397 // Fixed-length string.409 // Fixed-length string.
398410
399 var r: T = undefined;411 var r: T = undefined;
...@@ -437,7 +449,7 @@ pub fn innerParse(...@@ -437,7 +449,7 @@ pub fn innerParse(
437 return r;449 return r;
438 },450 },
439451
440 else => return error.UnexpectedToken,452 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
441 }453 }
442 },454 },
443455
...@@ -446,7 +458,7 @@ pub fn innerParse(...@@ -446,7 +458,7 @@ pub fn innerParse(
446 .array_begin => {458 .array_begin => {
447 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);459 return internalParseArray(T, vecInfo.child, vecInfo.len, allocator, source, options);
448 },460 },
449 else => return error.UnexpectedToken,461 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
450 }462 }
451 },463 },
452464
...@@ -485,7 +497,7 @@ pub fn innerParse(...@@ -485,7 +497,7 @@ pub fn innerParse(
485 return try arraylist.toOwnedSlice();497 return try arraylist.toOwnedSlice();
486 },498 },
487 .string => {499 .string => {
488 if (ptrInfo.child != u8) return error.UnexpectedToken;500 if (ptrInfo.child != u8) return typeError(allocator, options.diagnostics, .string, "array");
489501
490 // Dynamic length string.502 // Dynamic length string.
491 if (ptrInfo.sentinel) |sentinel_ptr| {503 if (ptrInfo.sentinel) |sentinel_ptr| {
...@@ -507,7 +519,7 @@ pub fn innerParse(...@@ -507,7 +519,7 @@ pub fn innerParse(
507 }519 }
508 }520 }
509 },521 },
510 else => return error.UnexpectedToken,522 else => |t| return typeError(allocator, options.diagnostics, t, "array"),
511 }523 }
512 },524 },
513 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),525 else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
...@@ -531,14 +543,67 @@ fn internalParseArray(...@@ -531,14 +543,67 @@ fn internalParseArray(
531 var r: T = undefined;543 var r: T = undefined;
532 var i: usize = 0;544 var i: usize = 0;
533 while (i < len) : (i += 1) {545 while (i < len) : (i += 1) {
546 if (.array_end == try source.peekNextTokenType()) return error.LengthMismatch;
534 r[i] = try innerParse(Child, allocator, source, options);547 r[i] = try innerParse(Child, allocator, source, options);
535 }548 }
536549
537 if (.array_end != try source.next()) return error.UnexpectedToken;550 if (.array_end != try source.next()) return error.LengthMismatch;
538551
539 return r;552 return r;
540}553}
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
542/// This is an internal function called recursively607/// This is an internal function called recursively
543/// during the implementation of `parseFromValueLeaky`.608/// during the implementation of `parseFromValueLeaky`.
544/// It is exposed primarily to enable custom `jsonParseFromValue()` methods to call back into the `parseFromValue*` system,609/// It is exposed primarily to enable custom `jsonParseFromValue()` methods to call back into the `parseFromValue*` system,