|
//! The simplest way to parse ZON at runtime is to use `fromSlice`. If you need to parse ZON at //! compile time, you may use `@import`. //! //! Parsing from individual Zoir nodes is also available: //! * `fromZoir` //! * `fromZoirNode` //! //! For lower level control, it is possible to operate on `std.zig.Zoir` directly. |
OptionsRename when adding or removing support for a type. |
const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const Ast = std.zig.Ast; const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; const TokenIndex = std.zig.Ast.TokenIndex; const Base = std.zig.number_literal.Base; const StrLitErr = std.zig.string_literal.Error; const NumberLiteralError = std.zig.number_literal.Error; const assert = std.debug.assert; const ArrayListUnmanaged = std.ArrayListUnmanaged; |
ErrorConfiguration for the runtime parser. |
/// Rename when adding or removing support for a type. const valid_types = {}; |
NoteIf true, unknown fields do not error. |
/// Configuration for the runtime parser. pub const Options = struct { /// If true, unknown fields do not error. ignore_unknown_fields: bool = false, /// If true, the parser cleans up partially parsed values on error. This requires some extra /// bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because /// you're using arena allocation.) free_on_error: bool = true, }; |
IteratorIf true, the parser cleans up partially parsed values on error. This requires some extra bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because you're using arena allocation.) |
pub const Error = union(enum) { zoir: Zoir.CompileError, type_check: Error.TypeCheckFailure, |
next()Information about the success or failure of a parse. |
pub const Note = union(enum) { zoir: Zoir.CompileError.Note, type_check: TypeCheckFailure.Note, |
fmtMessage()Parses the given slice as ZON.
Returns |
pub const Iterator = struct { index: usize = 0, err: Error, status: *const Status, |
getLocation()The type to deserialize into. May not be or contain any of the following types:
* Any comptime-only type, except in a comptime field
* |
pub fn next(self: *@This()) ?Note { switch (self.err) { .zoir => |err| { if (self.index >= err.note_count) return null; const zoir = self.status.zoir.?; const note = err.getNotes(zoir)[self.index]; self.index += 1; return .{ .zoir = note }; }, .type_check => |err| { if (self.index >= err.getNoteCount()) return null; const note = err.getNote(self.index); self.index += 1; return .{ .type_check = note }; }, } } }; |
IteratorLike |
fn formatMessage( self: []const u8, comptime f: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = f; _ = options; |
next()Like |
// Just writes the string for now, but we're keeping this behind a formatter so we have // the option to extend it in the future to print more advanced messages (like `Error` // does) without breaking the API. try writer.writeAll(self); } |
fmtMessage()Frees ZON values. Provided for convenience, you may also free these values on your own using the same allocator passed into the parser. Asserts at comptime that sufficient information is available via the type system to free this value. Untagged unions, for example, will fail this assert. |
pub fn fmtMessage(self: Note, status: *const Status) std.fmt.Formatter(Note.formatMessage) { return .{ .data = switch (self) { .zoir => |note| note.msg.get(status.zoir.?), .type_check => |note| note.msg, } }; } |
getLocation()Prints a message of the form |
pub fn getLocation(self: Note, status: *const Status) Ast.Location { const ast = status.ast.?; switch (self) { .zoir => |note| return zoirErrorLocation(ast, note.token, note.node_or_offset), .type_check => |note| return ast.tokenLocation(note.offset, note.token), } } }; |
iterateNotes()Visited structs and unions, to avoid infinite recursion. Tracking more types is unnecessary, and a little complex due to optional nesting. |
pub const Iterator = struct { index: usize = 0, status: *const Status, |
Status |
pub fn next(self: *@This()) ?Error { const zoir = self.status.zoir orelse return null; |
deinit() |
if (self.index < zoir.compile_errors.len) { const result: Error = .{ .zoir = zoir.compile_errors[self.index] }; self.index += 1; return result; } |
iterateErrors() |
if (self.status.type_check) |err| { if (self.index == zoir.compile_errors.len) { const result: Error = .{ .type_check = err }; self.index += 1; return result; } } |
format() |
return null; } }; |
fromSlice() |
const TypeCheckFailure = struct { const Note = struct { token: Ast.TokenIndex, offset: u32, msg: []const u8, owned: bool, |
fromZoir() |
fn deinit(self: @This(), gpa: Allocator) void { if (self.owned) gpa.free(self.msg); } }; |
fromZoirNode() |
message: []const u8, owned: bool, token: Ast.TokenIndex, offset: u32, note: ?@This().Note, |
free() |
fn deinit(self: @This(), gpa: Allocator) void { if (self.note) |note| note.deinit(gpa); if (self.owned) gpa.free(self.message); } |
Test:std.zon parse canParseType |
fn getNoteCount(self: @This()) usize { return @intFromBool(self.note != null); } |
Test:std.zon requiresAllocator |
fn getNote(self: @This(), index: usize) @This().Note { assert(index == 0); return self.note.?; } }; |
Test:std.zon ast errors |
const FormatMessage = struct { err: Error, status: *const Status, }; |
Test:std.zon comments |
fn formatMessage( self: FormatMessage, comptime f: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = f; _ = options; switch (self.err) { .zoir => |err| try writer.writeAll(err.msg.get(self.status.zoir.?)), .type_check => |tc| try writer.writeAll(tc.message), } } |
Test:std.zon failure/oom formatting |
pub fn fmtMessage(self: @This(), status: *const Status) std.fmt.Formatter(formatMessage) { return .{ .data = .{ .err = self, .status = status, } }; } |
Test:std.zon fromSlice syntax error |
pub fn getLocation(self: @This(), status: *const Status) Ast.Location { const ast = status.ast.?; return switch (self) { .zoir => |err| return zoirErrorLocation( status.ast.?, err.token, err.node_or_offset, ), .type_check => |err| return ast.tokenLocation(err.offset, err.token), }; } |
Test:std.zon optional |
pub fn iterateNotes(self: @This(), status: *const Status) Note.Iterator { return .{ .err = self, .status = status }; } |
Test:std.zon unions |
fn zoirErrorLocation(ast: Ast, maybe_token: Ast.TokenIndex, node_or_offset: u32) Ast.Location { if (maybe_token == Zoir.CompileError.invalid_token) { const main_tokens = ast.nodes.items(.main_token); const ast_node = node_or_offset; const token = main_tokens[ast_node]; return ast.tokenLocation(0, token); } else { var location = ast.tokenLocation(0, maybe_token); location.column += node_or_offset; return location; } } }; |
Test:std.zon structs |
/// Information about the success or failure of a parse. pub const Status = struct { ast: ?Ast = null, zoir: ?Zoir = null, type_check: ?Error.TypeCheckFailure = null, |
Test:std.zon tuples |
fn assertEmpty(self: Status) void { assert(self.ast == null); assert(self.zoir == null); assert(self.type_check == null); } |
Test:std.zon arrays and slices |
pub fn deinit(self: *Status, gpa: Allocator) void { if (self.ast) |*ast| ast.deinit(gpa); if (self.zoir) |*zoir| zoir.deinit(gpa); if (self.type_check) |tc| tc.deinit(gpa); self.* = undefined; } |
Test:std.zon string literal |
pub fn iterateErrors(self: *const Status) Error.Iterator { return .{ .status = self }; } |
Test:std.zon enum literals |
pub fn format( self: *const @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; var errors = self.iterateErrors(); while (errors.next()) |err| { const loc = err.getLocation(self); const msg = err.fmtMessage(self); try writer.print("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, msg }); |
Test:std.zon parse bool |
var notes = err.iterateNotes(self); while (notes.next()) |note| { const note_loc = note.getLocation(self); const note_msg = note.fmtMessage(self); try writer.print("{}:{}: note: {s}\n", .{ note_loc.line + 1, note_loc.column + 1, note_msg, }); } } } }; |
Test:std.zon intFromFloatExact |
/// Parses the given slice as ZON. /// /// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is /// invalid or can not be deserialized into type `T`. /// /// When the parser returns `error.ParseZon`, it will also store a human readable explanation in /// `status` if non null. If status is not null, it must be initialized to `.{}`. pub fn fromSlice( /// The type to deserialize into. May not be or contain any of the following types: /// * Any comptime-only type, except in a comptime field /// * `type` /// * `void`, except as a union payload /// * `noreturn` /// * An error set/error union /// * A many-pointer or C-pointer /// * An opaque type, including `anyopaque` /// * An async frame type, including `anyframe` and `anyframe->T` /// * A function /// /// All other types are valid. Unsupported types will fail at compile time. T: type, gpa: Allocator, source: [:0]const u8, status: ?*Status, options: Options, ) error{ OutOfMemory, ParseZon }!T { if (status) |s| s.assertEmpty(); |
Test:std.zon parse int |
var ast = try std.zig.Ast.parse(gpa, source, .zon); defer if (status == null) ast.deinit(gpa); if (status) |s| s.ast = ast; |
Test:std.zon negative char |
// If there's no status, Zoir exists for the lifetime of this function. If there is a status, // ownership is transferred to status. var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer if (status == null) zoir.deinit(gpa); |
Test:std.zon parse float |
if (status) |s| s.* = .{}; return fromZoir(T, gpa, ast, zoir, status, options); } |
Test:std.zon free on error |
/// Like `fromSlice`, but operates on `Zoir` instead of ZON source. pub fn fromZoir( T: type, gpa: Allocator, ast: Ast, zoir: Zoir, status: ?*Status, options: Options, ) error{ OutOfMemory, ParseZon }!T { return fromZoirNode(T, gpa, ast, zoir, .root, status, options); } |
Test:std.zon vector |
/// Like `fromZoir`, but the parse starts on `node` instead of root. pub fn fromZoirNode( T: type, gpa: Allocator, ast: Ast, zoir: Zoir, node: Zoir.Node.Index, status: ?*Status, options: Options, ) error{ OutOfMemory, ParseZon }!T { comptime assert(canParseType(T)); |
Test:std.zon add pointers |
if (status) |s| { s.assertEmpty(); s.ast = ast; s.zoir = zoir; } if (zoir.hasCompileErrors()) { return error.ParseZon; } var parser: Parser = .{ .gpa = gpa, .ast = ast, .zoir = zoir, .options = options, .status = status, }; return parser.parseExpr(T, node); } /// Frees ZON values. /// /// Provided for convenience, you may also free these values on your own using the same allocator /// passed into the parser. /// /// Asserts at comptime that sufficient information is available via the type system to free this /// value. Untagged unions, for example, will fail this assert. pub fn free(gpa: Allocator, value: anytype) void { const Value = @TypeOf(value); _ = valid_types; switch (@typeInfo(Value)) { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { switch (pointer.size) { .one => { free(gpa, value.*); gpa.destroy(value); }, .slice => { for (value) |item| { free(gpa, item); } gpa.free(value); }, .many, .c => comptime unreachable, } }, .array => for (value) |item| { free(gpa, item); }, .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { free(gpa, @field(value, field.name)); }, .@"union" => |@"union"| if (@"union".tag_type == null) { if (comptime requiresAllocator(Value)) unreachable; } else switch (value) { inline else => |_, tag| { free(gpa, @field(value, @tagName(tag))); }, }, .optional => if (value) |some| { free(gpa, some); }, .vector => |vector| for (0..vector.len) |i| free(gpa, value[i]), .void => {}, else => comptime unreachable, } } fn requiresAllocator(T: type) bool { _ = valid_types; return switch (@typeInfo(T)) { .pointer => true, .array => |array| return array.len > 0 and requiresAllocator(array.child), .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { if (requiresAllocator(field.type)) { break true; } } else false, .@"union" => |@"union"| inline for (@"union".fields) |field| { if (requiresAllocator(field.type)) { break true; } } else false, .optional => |optional| requiresAllocator(optional.child), .vector => |vector| return vector.len > 0 and requiresAllocator(vector.child), else => false, }; } const Parser = struct { gpa: Allocator, ast: Ast, zoir: Zoir, status: ?*Status, options: Options, fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T { return self.parseExprInner(T, node) catch |err| switch (err) { error.WrongType => return self.failExpectedType(T, node), else => |e| return e, }; } fn parseExprInner( self: *@This(), T: type, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory, WrongType }!T { switch (@typeInfo(T)) { .optional => |optional| if (node.get(self.zoir) == .null) { return null; } else { return try self.parseExprInner(optional.child, node); }, .bool => return self.parseBool(node), .int => return self.parseInt(T, node), .float => return self.parseFloat(T, node), .@"enum" => return self.parseEnumLiteral(T, node), .pointer => |pointer| switch (pointer.size) { .one => { const result = try self.gpa.create(pointer.child); errdefer self.gpa.destroy(result); result.* = try self.parseExprInner(pointer.child, node); return result; }, .slice => return self.parseSlicePointer(T, node), else => comptime unreachable, }, .array => return self.parseArray(T, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) return self.parseTuple(T, node) else return self.parseStruct(T, node), .@"union" => return self.parseUnion(T, node), .vector => return self.parseVector(T, node), else => comptime unreachable, } } /// Prints a message of the form `expected T` where T is first converted to a ZON type. For /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just /// referred to by the type of container. fn failExpectedType( self: @This(), T: type, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory } { @branchHint(.cold); return self.failExpectedTypeInner(T, false, node); } fn failExpectedTypeInner( self: @This(), T: type, opt: bool, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory } { _ = valid_types; switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { if (opt) { return self.failNode(node, "expected optional tuple"); } else { return self.failNode(node, "expected tuple"); } } else { if (opt) { return self.failNode(node, "expected optional struct"); } else { return self.failNode(node, "expected struct"); } }, .@"union" => if (opt) { return self.failNode(node, "expected optional union"); } else { return self.failNode(node, "expected union"); }, .array => if (opt) { return self.failNode(node, "expected optional array"); } else { return self.failNode(node, "expected array"); }, .pointer => |pointer| switch (pointer.size) { .one => return self.failExpectedTypeInner(pointer.child, opt, node), .slice => { if (pointer.child == u8 and pointer.is_const and (pointer.sentinel() == null or pointer.sentinel() == 0) and pointer.alignment == 1) { if (opt) { return self.failNode(node, "expected optional string"); } else { return self.failNode(node, "expected string"); } } else { if (opt) { return self.failNode(node, "expected optional array"); } else { return self.failNode(node, "expected array"); } } }, else => comptime unreachable, }, .vector, .bool, .int, .float => if (opt) { return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)}); } else { return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}); }, .@"enum" => if (opt) { return self.failNode(node, "expected optional enum literal"); } else { return self.failNode(node, "expected enum literal"); }, .optional => |optional| { return self.failExpectedTypeInner(optional.child, true, node); }, else => comptime unreachable, } } fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { switch (node.get(self.zoir)) { .true => return true, .false => return false, else => return error.WrongType, } } fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), .big => |val| return val.toInt(T) catch self.failCannotRepresent(T, node), }, .float_literal => |val| return intFromFloatExact(T, val) orelse self.failCannotRepresent(T, node), .char_literal => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), else => return error.WrongType, } } fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return @floatFromInt(val), .big => |val| return val.toFloat(T), }, .float_literal => |val| return @floatCast(val), .pos_inf => return std.math.inf(T), .neg_inf => return -std.math.inf(T), .nan => return std.math.nan(T), .char_literal => |val| return @floatFromInt(val), else => return error.WrongType, } } fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .enum_literal => |field_name| { // Create a comptime string map for the enum fields const enum_fields = @typeInfo(T).@"enum".fields; comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; inline for (enum_fields, 0..) |field, i| { kvs_list[i] = .{ field.name, @enumFromInt(field.value) }; } const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); // Get the tag if it exists const field_name_str = field_name.get(self.zoir); return enum_tags.get(field_name_str) orelse self.failUnexpected(T, "enum literal", node, null, field_name_str); }, else => return error.WrongType, } } fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .string_literal => return self.parseString(T, node), .array_literal => |nodes| return self.parseSlice(T, nodes), .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }), else => return error.WrongType, } } fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T { const ast_node = node.getAstNode(self.zoir); const pointer = @typeInfo(T).pointer; var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); if (pointer.sentinel() != null) size_hint += 1; var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint); defer buf.deinit(self.gpa); switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) { .success => {}, .failure => |err| { const token = self.ast.nodes.items(.main_token)[ast_node]; const raw_string = self.ast.tokenSlice(token); return self.failTokenFmt(token, @intCast(err.offset()), "{s}", .{err.fmt(raw_string)}); }, } if (pointer.child != u8 or pointer.size != .slice or !pointer.is_const or (pointer.sentinel() != null and pointer.sentinel() != 0) or pointer.alignment != 1) { return error.WrongType; } if (pointer.sentinel() != null) { return buf.toOwnedSliceSentinel(self.gpa, 0); } else { return buf.toOwnedSlice(self.gpa); } } fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T { const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice switch (pointer.size) { .slice => {}, .one, .many, .c => comptime unreachable, } // Allocate the slice const slice = try self.gpa.allocWithOptions( pointer.child, nodes.len, pointer.alignment, pointer.sentinel(), ); errdefer self.gpa.free(slice); // Parse the elements and return the slice for (slice, 0..) |*elem, i| { errdefer if (self.options.free_on_error) { for (slice[0..i]) |item| { free(self.gpa, item); } }; elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i))); } return slice; } fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, }; const array_info = @typeInfo(T).array; // Check if the size matches if (nodes.len < array_info.len) { return self.failNodeFmt( node, "expected {} array elements; found {}", .{ array_info.len, nodes.len }, ); } else if (nodes.len > array_info.len) { return self.failNodeFmt( nodes.at(array_info.len), "index {} outside of array of length {}", .{ array_info.len, array_info.len }, ); } // Parse the elements and return the array var result: T = undefined; for (&result, 0..) |*elem, i| { // If we fail to parse this field, free all fields before it errdefer if (self.options.free_on_error) { for (result[0..i]) |item| { free(self.gpa, item); } }; elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i))); } return result; } fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T { const repr = node.get(self.zoir); const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return error.WrongType, }; const field_infos = @typeInfo(T).@"struct".fields; // Build a map from field name to index. // The special value `comptime_field` indicates that this is actually a comptime field. const comptime_field = std.math.maxInt(usize); const field_indices: std.StaticStringMap(usize) = comptime b: { var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; for (&kvs_list, field_infos, 0..) |*kv, field, i| { kv.* = .{ field.name, if (field.is_comptime) comptime_field else i }; } break :b .initComptime(kvs_list); }; // Parse the struct var result: T = undefined; var field_found: [field_infos.len]bool = @splat(false); // If we fail partway through, free all already initialized fields var initialized: usize = 0; errdefer if (self.options.free_on_error and field_infos.len > 0) { for (fields.names[0..initialized]) |name_runtime| { switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { inline 0...(field_infos.len - 1) => |name_index| { const name = field_infos[name_index].name; free(self.gpa, @field(result, name)); }, else => unreachable, // Can't be out of bounds } } }; // Fill in the fields we found for (0..fields.names.len) |i| { const name = fields.names[i].get(self.zoir); const field_index = field_indices.get(name) orelse { if (self.options.ignore_unknown_fields) continue; return self.failUnexpected(T, "field", node, i, name); }; if (field_index == comptime_field) { return self.failComptimeField(node, i); } // Mark the field as found. Assert that the found array is not zero length to satisfy // the type checker (it can't be since we made it into an iteration of this loop.) if (field_found.len == 0) unreachable; field_found[field_index] = true; switch (field_index) { inline 0...(field_infos.len - 1) => |j| { if (field_infos[j].is_comptime) unreachable; @field(result, field_infos[j].name) = try self.parseExpr( field_infos[j].type, fields.vals.at(@intCast(i)), ); }, else => unreachable, // Can't be out of bounds } initialized += 1; } // Fill in any missing default fields inline for (field_found, 0..) |found, i| { if (!found) { const field_info = field_infos[i]; if (field_info.default_value_ptr) |default| { const typed: *const field_info.type = @ptrCast(@alignCast(default)); @field(result, field_info.name) = typed.*; } else { return self.failNodeFmt( node, "missing required field {s}", .{field_infos[i].name}, ); } } } return result; } fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, }; var result: T = undefined; const field_infos = @typeInfo(T).@"struct".fields; if (nodes.len > field_infos.len) { return self.failNodeFmt( nodes.at(field_infos.len), "index {} outside of tuple length {}", .{ field_infos.len, field_infos.len }, ); } inline for (0..field_infos.len) |i| { // Check if we're out of bounds if (i >= nodes.len) { if (field_infos[i].default_value_ptr) |default| { const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); @field(result, field_infos[i].name) = typed.*; } else { return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); } } else { // If we fail to parse this field, free all fields before it errdefer if (self.options.free_on_error) { inline for (0..i) |j| { if (j >= i) break; free(self.gpa, result[j]); } }; if (field_infos[i].is_comptime) { return self.failComptimeField(node, i); } else { result[i] = try self.parseExpr(field_infos[i].type, nodes.at(i)); } } } return result; } fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; if (field_infos.len == 0) comptime unreachable; // Gather info on the fields const field_indices = b: { comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; inline for (field_infos, 0..) |field, i| { kvs_list[i] = .{ field.name, i }; } break :b std.StaticStringMap(usize).initComptime(kvs_list); }; // Parse the union switch (node.get(self.zoir)) { .enum_literal => |field_name| { // The union must be tagged for an enum literal to coerce to it if (@"union".tag_type == null) { return error.WrongType; } // Get the index of the named field. We don't use `parseEnum` here as // the order of the enum and the order of the union might not match! const field_index = b: { const field_name_str = field_name.get(self.zoir); break :b field_indices.get(field_name_str) orelse return self.failUnexpected(T, "field", node, null, field_name_str); }; // Initialize the union from the given field. switch (field_index) { inline 0...field_infos.len - 1 => |i| { // Fail if the field is not void if (field_infos[i].type != void) return self.failNode(node, "expected union"); // Instantiate the union return @unionInit(T, field_infos[i].name, {}); }, else => unreachable, // Can't be out of bounds } }, .struct_literal => |struct_fields| { if (struct_fields.names.len != 1) { return error.WrongType; } // Fill in the field we found const field_name = struct_fields.names[0]; const field_name_str = field_name.get(self.zoir); const field_val = struct_fields.vals.at(0); const field_index = field_indices.get(field_name_str) orelse return self.failUnexpected(T, "field", node, 0, field_name_str); switch (field_index) { inline 0...field_infos.len - 1 => |i| { if (field_infos[i].type == void) { return self.failNode(field_val, "expected type 'void'"); } else { const value = try self.parseExpr(field_infos[i].type, field_val); return @unionInit(T, field_infos[i].name, value); } }, else => unreachable, // Can't be out of bounds } }, else => return error.WrongType, } } fn parseVector( self: *@This(), T: type, node: Zoir.Node.Index, ) !T { const vector_info = @typeInfo(T).vector; const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, }; var result: T = undefined; if (nodes.len != vector_info.len) { return self.failNodeFmt( node, "expected {} vector elements; found {}", .{ vector_info.len, nodes.len }, ); } for (0..vector_info.len) |i| { errdefer for (0..i) |j| free(self.gpa, result[j]); result[i] = try self.parseExpr(vector_info.child, nodes.at(@intCast(i))); } return result; } fn failTokenFmt( self: @This(), token: Ast.TokenIndex, offset: u32, comptime fmt: []const u8, args: anytype, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); return self.failTokenFmtNote(token, offset, fmt, args, null); } fn failTokenFmtNote( self: @This(), token: Ast.TokenIndex, offset: u32, comptime fmt: []const u8, args: anytype, note: ?Error.TypeCheckFailure.Note, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); comptime assert(args.len > 0); if (self.status) |s| s.type_check = .{ .token = token, .offset = offset, .message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| { if (note) |n| n.deinit(self.gpa); return err; }, .owned = true, .note = note, }; return error.ParseZon; } fn failNodeFmt( self: @This(), node: Zoir.Node.Index, comptime fmt: []const u8, args: anytype, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const main_tokens = self.ast.nodes.items(.main_token); const token = main_tokens[node.getAstNode(self.zoir)]; return self.failTokenFmt(token, 0, fmt, args); } fn failToken( self: @This(), failure: Error.TypeCheckFailure, ) error{ParseZon} { @branchHint(.cold); if (self.status) |s| s.type_check = failure; return error.ParseZon; } fn failNode( self: @This(), node: Zoir.Node.Index, message: []const u8, ) error{ParseZon} { @branchHint(.cold); const main_tokens = self.ast.nodes.items(.main_token); const token = main_tokens[node.getAstNode(self.zoir)]; return self.failToken(.{ .token = token, .offset = 0, .message = message, .owned = false, .note = null, }); } fn failCannotRepresent( self: @This(), T: type, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); } fn failUnexpected( self: @This(), T: type, item_kind: []const u8, node: Zoir.Node.Index, field: ?usize, name: []const u8, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = if (field) |f| b: { var buf: [2]Ast.Node.Index = undefined; const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; const field_node = struct_init.ast.fields[f]; break :b self.ast.firstToken(field_node) - 2; } else b: { const main_tokens = self.ast.nodes.items(.main_token); break :b main_tokens[node.getAstNode(self.zoir)]; }; switch (@typeInfo(T)) { inline .@"struct", .@"union", .@"enum" => |info| { const note: Error.TypeCheckFailure.Note = if (info.fields.len == 0) b: { break :b .{ .token = token, .offset = 0, .msg = "none expected", .owned = false, }; } else b: { const msg = "supported: "; var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64); defer buf.deinit(self.gpa); const writer = buf.writer(self.gpa); try writer.writeAll(msg); inline for (info.fields, 0..) |field_info, i| { if (i != 0) try writer.writeAll(", "); try writer.print("'{p_}'", .{std.zig.fmtId(field_info.name)}); } break :b .{ .token = token, .offset = 0, .msg = try buf.toOwnedSlice(self.gpa), .owned = true, }; }; return self.failTokenFmtNote( token, 0, "unexpected {s} '{s}'", .{ item_kind, name }, note, ); }, else => comptime unreachable, } } // Technically we could do this if we were willing to do a deep equal to verify // the value matched, but doing so doesn't seem to support any real use cases // so isn't worth the complexity at the moment. fn failComptimeField( self: @This(), node: Zoir.Node.Index, field: usize, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const ast_node = node.getAstNode(self.zoir); var buf: [2]Ast.Node.Index = undefined; const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: { const field_node = struct_init.ast.fields[field]; break :b self.ast.firstToken(field_node); } else b: { const array_init = self.ast.fullArrayInit(&buf, ast_node).?; const value_node = array_init.ast.elements[field]; break :b self.ast.firstToken(value_node); }; return self.failToken(.{ .token = token, .offset = 0, .message = "cannot initialize comptime field", .owned = false, .note = null, }); } }; fn intFromFloatExact(T: type, value: anytype) ?T { if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { return null; } if (std.math.isNan(value) or std.math.trunc(value) != value) { return null; } return @intFromFloat(value); } fn canParseType(T: type) bool { comptime return canParseTypeInner(T, &.{}, false); } fn canParseTypeInner( T: type, /// Visited structs and unions, to avoid infinite recursion. /// Tracking more types is unnecessary, and a little complex due to optional nesting. visited: []const type, parent_is_optional: bool, ) bool { return switch (@typeInfo(T)) { .bool, .int, .float, .null, .@"enum", => true, .noreturn, .void, .type, .undefined, .error_union, .error_set, .@"fn", .frame, .@"anyframe", .@"opaque", .comptime_int, .comptime_float, .enum_literal, => false, .pointer => |pointer| switch (pointer.size) { .one => canParseTypeInner(pointer.child, visited, parent_is_optional), .slice => canParseTypeInner(pointer.child, visited, false), .many, .c => false, }, .optional => |optional| if (parent_is_optional) false else canParseTypeInner(optional.child, visited, true), .array => |array| canParseTypeInner(array.child, visited, false), .vector => |vector| canParseTypeInner(vector.child, visited, false), .@"struct" => |@"struct"| { for (visited) |V| if (T == V) return true; const new_visited = visited ++ .{T}; for (@"struct".fields) |field| { if (!field.is_comptime and !canParseTypeInner(field.type, new_visited, false)) { return false; } } return true; }, .@"union" => |@"union"| { for (visited) |V| if (T == V) return true; const new_visited = visited ++ .{T}; for (@"union".fields) |field| { if (field.type != void and !canParseTypeInner(field.type, new_visited, false)) { return false; } } return true; }, }; } test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(void)); try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); try std.testing.expect(!comptime canParseType(struct { error{foo} })); try std.testing.expect(!comptime canParseType(union(enum) { a: void, b: [*c]u8 })); try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8))); try std.testing.expect(!comptime canParseType(*?[*c]u8)); try std.testing.expect(comptime canParseType(enum(u8) { _ })); try std.testing.expect(comptime canParseType(union { foo: void })); try std.testing.expect(comptime canParseType(union(enum) { foo: void })); try std.testing.expect(!comptime canParseType(comptime_float)); try std.testing.expect(!comptime canParseType(comptime_int)); try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); try std.testing.expect(comptime canParseType(?u8)); try std.testing.expect(comptime canParseType(*?*u8)); try std.testing.expect(comptime canParseType(?struct { foo: ?struct { ?union(enum) { a: ?@Vector(0, ?*u8), }, ?struct { f: ?[]?u8, }, }, })); try std.testing.expect(!comptime canParseType(??u8)); try std.testing.expect(!comptime canParseType(?*?u8)); try std.testing.expect(!comptime canParseType(*?*?*u8)); try std.testing.expect(!comptime canParseType(struct { x: comptime_int = 2 })); try std.testing.expect(!comptime canParseType(struct { x: comptime_float = 2 })); try std.testing.expect(comptime canParseType(struct { comptime x: @TypeOf(.foo) = .foo })); try std.testing.expect(!comptime canParseType(struct { comptime_int })); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canParseType(Recursive)); // Make sure we validate nested optional before we early out due to already having seen // a type recursion! try std.testing.expect(!comptime canParseType(struct { add_to_visited: ?u8, retrieve_from_visited: ??u8, })); } test "std.zon requiresAllocator" { try std.testing.expect(!requiresAllocator(u8)); try std.testing.expect(!requiresAllocator(f32)); try std.testing.expect(!requiresAllocator(enum { foo })); try std.testing.expect(!requiresAllocator(struct { f32 })); try std.testing.expect(!requiresAllocator(struct { x: f32 })); try std.testing.expect(!requiresAllocator([0][]const u8)); try std.testing.expect(!requiresAllocator([2]u8)); try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(?f32)); try std.testing.expect(!requiresAllocator(void)); try std.testing.expect(!requiresAllocator(@TypeOf(null))); try std.testing.expect(!requiresAllocator(@Vector(3, u8))); try std.testing.expect(!requiresAllocator(@Vector(0, *const u8))); try std.testing.expect(requiresAllocator([]u8)); try std.testing.expect(requiresAllocator(*struct { u8, u8 })); try std.testing.expect(requiresAllocator([1][]const u8)); try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(?[]u8)); try std.testing.expect(requiresAllocator(@Vector(3, *const u8))); } test "std.zon ast errors" { const gpa = std.testing.allocator; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), ); try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status}); } test "std.zon comments" { const gpa = std.testing.allocator; try std.testing.expectEqual(@as(u8, 10), fromSlice(u8, gpa, \\// comment \\10 // comment \\// comment , null, .{})); { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, \\//! comment \\10 // comment \\// comment , &status, .{})); try std.testing.expectFmt( "1:1: error: expected expression, found 'a document comment'\n", "{}", .{status}, ); } } test "std.zon failure/oom formatting" { const gpa = std.testing.allocator; var failing_allocator = std.testing.FailingAllocator.init(gpa, .{ .fail_index = 0, .resize_fail_index = 0, }); var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.OutOfMemory, fromSlice( []const u8, failing_allocator.allocator(), "\"foo\"", &status, .{}, )); try std.testing.expectFmt("", "{}", .{status}); } test "std.zon fromSlice syntax error" { try std.testing.expectError( error.ParseZon, fromSlice(u8, std.testing.allocator, ".{", null, .{}), ); } test "std.zon optional" { const gpa = std.testing.allocator; // Basic usage { const none = try fromSlice(?u32, gpa, "null", null, .{}); try std.testing.expect(none == null); const some = try fromSlice(?u32, gpa, "1", null, .{}); try std.testing.expect(some.? == 1); } // Deep free { const none = try fromSlice(?[]const u8, gpa, "null", null, .{}); try std.testing.expect(none == null); const some = try fromSlice(?[]const u8, gpa, "\"foo\"", null, .{}); defer free(gpa, some); try std.testing.expectEqualStrings("foo", some.?); } } test "std.zon unions" { const gpa = std.testing.allocator; // Unions { const Tagged = union(enum) { x: f32, @"y y": bool, z, @"z z" }; const Untagged = union { x: f32, @"y y": bool, z: void, @"z z": void }; const tagged_x = try fromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{}); try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x); const tagged_y = try fromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{}); try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y); const tagged_z_shorthand = try fromSlice(Tagged, gpa, ".z", null, .{}); try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand); const tagged_zz_shorthand = try fromSlice(Tagged, gpa, ".@\"z z\"", null, .{}); try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand); const untagged_x = try fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); try std.testing.expect(untagged_x.x == 1.5); const untagged_y = try fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); try std.testing.expect(untagged_y.@"y y"); } // Deep free { const Union = union(enum) { bar: []const u8, baz: bool }; const noalloc = try fromSlice(Union, gpa, ".{.baz = false}", null, .{}); try std.testing.expectEqual(Union{ .baz = false }, noalloc); const alloc = try fromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{}); defer free(gpa, alloc); try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); } // Unknown field { const Union = union { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), ); try std.testing.expectFmt( \\1:4: error: unexpected field 'z' \\1:4: note: supported: 'x', 'y' \\ , "{}", .{status}, ); } // Explicit void field { const Union = union(enum) { x: void }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Union, gpa, ".{.x=1}", &status, .{}), ); try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status}); } // Extra field { const Union = union { x: f32, y: bool }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // No fields { const Union = union { x: f32, y: bool }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Union, gpa, ".{}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // Enum literals cannot coerce into untagged unions { const Union = union { x: void }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // Unknown field for enum literal coercion { const Union = union(enum) { x: void }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".y", &status, .{})); try std.testing.expectFmt( \\1:2: error: unexpected field 'y' \\1:2: note: supported: 'x' \\ , "{}", .{status}, ); } // Non void field for enum literal coercion { const Union = union(enum) { x: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } } test "std.zon structs" { const gpa = std.testing.allocator; // Structs (various sizes tested since they're parsed differently) { const Vec0 = struct {}; const Vec1 = struct { x: f32 }; const Vec2 = struct { x: f32, y: f32 }; const Vec3 = struct { x: f32, y: f32, z: f32 }; const zero = try fromSlice(Vec0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Vec0{}, zero); const one = try fromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); const two = try fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two); const three = try fromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{}); try std.testing.expectEqual(Vec3{ .x = 1.2, .y = 3.4, .z = 5.6 }, three); } // Deep free (structs and arrays) { const Foo = struct { bar: []const u8, baz: []const []const u8 }; const parsed = try fromSlice( Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", null, .{}, ); defer free(gpa, parsed); try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); } // Unknown field { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); try std.testing.expectFmt( \\1:12: error: unexpected field 'z' \\1:12: note: supported: 'x', 'y' \\ , "{}", .{status}, ); } // Duplicate field { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5, .x=3.5}", &status, .{}), ); try std.testing.expectFmt( \\1:4: error: duplicate struct field name \\1:12: note: duplicate name here \\ , "{}", .{status}); } // Ignore unknown fields { const Vec2 = struct { x: f32, y: f32 = 2.0 }; const parsed = try fromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", null, .{ .ignore_unknown_fields = true, }); try std.testing.expectEqual(Vec2{ .x = 1.0, .y = 2.0 }, parsed); } // Unknown field when struct has no fields (regression test) { const Vec2 = struct {}; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); try std.testing.expectFmt( \\1:4: error: unexpected field 'x' \\1:4: note: none expected \\ , "{}", .{status}); } // Missing field { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}), ); try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status}); } // Default field { const Vec2 = struct { x: f32, y: f32 = 1.5 }; const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } // Comptime field { const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } // Comptime field assignment { const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:18: error: cannot initialize comptime field \\ , "{}", .{status}); } // Enum field (regression test, we were previously getting the field name in an // incorrect way that broke for enum values) { const Vec0 = struct { x: enum { x } }; const parsed = try fromSlice(Vec0, gpa, ".{ .x = .x }", null, .{}); try std.testing.expectEqual(Vec0{ .x = .x }, parsed); } // Enum field and struct field with @ { const Vec0 = struct { @"x x": enum { @"x x" } }; const parsed = try fromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); } // Type expressions are not allowed { // Structs { var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice(struct {}, gpa, "Empty{}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' \\ , "{}", .{status}); } // Arrays { var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice([3]u8, gpa, "[3]u8{1, 2, 3}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' \\ , "{}", .{status}); } // Slices { var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice([]u8, gpa, "[]u8{1, 2, 3}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' \\ , "{}", .{status}); } // Tuples { var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice( struct { u8, u8, u8 }, gpa, "Tuple{1, 2, 3}", &status, .{}, ); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' \\ , "{}", .{status}); } // Nested { var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice(struct {}, gpa, ".{ .x = Tuple{1, 2, 3} }", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:9: error: types are not available in ZON \\1:9: note: replace the type with '.' \\ , "{}", .{status}); } } } test "std.zon tuples" { const gpa = std.testing.allocator; // Structs (various sizes tested since they're parsed differently) { const Tuple0 = struct {}; const Tuple1 = struct { f32 }; const Tuple2 = struct { f32, bool }; const Tuple3 = struct { f32, bool, u8 }; const zero = try fromSlice(Tuple0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Tuple0{}, zero); const one = try fromSlice(Tuple1, gpa, ".{1.2}", null, .{}); try std.testing.expectEqual(Tuple1{1.2}, one); const two = try fromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); try std.testing.expectEqual(Tuple2{ 1.2, true }, two); const three = try fromSlice(Tuple3, gpa, ".{1.2, false, 3}", null, .{}); try std.testing.expectEqual(Tuple3{ 1.2, false, 3 }, three); } // Deep free { const Tuple = struct { []const u8, []const u8 }; const parsed = try fromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); defer free(gpa, parsed); try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); } // Extra field { const Tuple = struct { f32, bool }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}), ); try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status}); } // Extra field { const Tuple = struct { f32, bool }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Tuple, gpa, ".{0.5}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: missing tuple field with index 1\n", "{}", .{status}, ); } // Tuple with unexpected field names { const Tuple = struct { f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status}); } // Struct with missing field names { const Struct = struct { foo: f32 }; var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Struct, gpa, ".{10.0}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status}); } // Comptime field { const Vec2 = struct { f32, comptime f32 = 1.5 }; const parsed = try fromSlice(Vec2, gpa, ".{ 1.2 }", null, .{}); try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed); } // Comptime field assignment { const Vec2 = struct { f32, comptime f32 = 1.5 }; var status: Status = .{}; defer status.deinit(gpa); const parsed = fromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:9: error: cannot initialize comptime field \\ , "{}", .{status}); } } // Test sizes 0 to 3 since small sizes get parsed differently test "std.zon arrays and slices" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/20881 const gpa = std.testing.allocator; // Literals { // Arrays { const zero = try fromSlice([0]u8, gpa, ".{}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); const one = try fromSlice([1]u8, gpa, ".{'a'}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); const two = try fromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two); const two_comma = try fromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma); const three = try fromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); const sentinel = try fromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", null, .{}); const expected_sentinel: [3:'z']u8 = .{ 'a', 'b', 'c' }; try std.testing.expectEqualSlices(u8, &expected_sentinel, &sentinel); } // Slice literals { const zero = try fromSlice([]const u8, gpa, ".{}", null, .{}); defer free(gpa, zero); try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); const one = try fromSlice([]u8, gpa, ".{'a'}", null, .{}); defer free(gpa, one); try std.testing.expectEqualSlices(u8, &.{'a'}, one); const two = try fromSlice([]const u8, gpa, ".{'a', 'b'}", null, .{}); defer free(gpa, two); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two); const two_comma = try fromSlice([]const u8, gpa, ".{'a', 'b',}", null, .{}); defer free(gpa, two_comma); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma); const three = try fromSlice([]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); defer free(gpa, three); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three); const sentinel = try fromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{}); defer free(gpa, sentinel); const expected_sentinel: [:'z']const u8 = &.{ 'a', 'b', 'c' }; try std.testing.expectEqualSlices(u8, expected_sentinel, sentinel); } } // Deep free { // Arrays { const parsed = try fromSlice([1][]const u8, gpa, ".{\"abc\"}", null, .{}); defer free(gpa, parsed); const expected: [1][]const u8 = .{"abc"}; try std.testing.expectEqualDeep(expected, parsed); } // Slice literals { const parsed = try fromSlice([]const []const u8, gpa, ".{\"abc\"}", null, .{}); defer free(gpa, parsed); const expected: []const []const u8 = &.{"abc"}; try std.testing.expectEqualDeep(expected, parsed); } } // Sentinels and alignment { // Arrays { const sentinel = try fromSlice([1:2]u8, gpa, ".{1}", null, .{}); try std.testing.expectEqual(@as(usize, 1), sentinel.len); try std.testing.expectEqual(@as(u8, 1), sentinel[0]); try std.testing.expectEqual(@as(u8, 2), sentinel[1]); } // Slice literals { const sentinel = try fromSlice([:2]align(4) u8, gpa, ".{1}", null, .{}); defer free(gpa, sentinel); try std.testing.expectEqual(@as(usize, 1), sentinel.len); try std.testing.expectEqual(@as(u8, 1), sentinel[0]); try std.testing.expectEqual(@as(u8, 2), sentinel[1]); } } // Expect 0 find 3 { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt( "1:3: error: index 0 outside of array of length 0\n", "{}", .{status}, ); } // Expect 1 find 2 { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), ); try std.testing.expectFmt( "1:8: error: index 1 outside of array of length 1\n", "{}", .{status}, ); } // Expect 2 find 1 { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([2]u8, gpa, ".{'a'}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: expected 2 array elements; found 1\n", "{}", .{status}, ); } // Expect 3 find 0 { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([3]u8, gpa, ".{}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: expected 3 array elements; found 0\n", "{}", .{status}, ); } // Wrong inner type { // Array { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } // Slice { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } } // Complete wrong type { // Array { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([3]u8, gpa, "'a'", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } // Slice { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]u8, gpa, "'a'", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Address of is not allowed (indirection for slices in ZON is implicit) { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt( "1:3: error: pointers are not available in ZON\n", "{}", .{status}, ); } } test "std.zon string literal" { const gpa = std.testing.allocator; // Basic string literal { const parsed = try fromSlice([]const u8, gpa, "\"abc\"", null, .{}); defer free(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed); } // String literal with escape characters { const parsed = try fromSlice([]const u8, gpa, "\"ab\\nc\"", null, .{}); defer free(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed); } // String literal with embedded null { const parsed = try fromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{}); defer free(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); } // Passing string literal to a mutable slice { { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]u8, gpa, "\"abcd\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]u8, gpa, "\\\\abcd", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Passing string literal to a array { { var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Zero terminated slices { { const parsed: [:0]const u8 = try fromSlice( [:0]const u8, gpa, "\"abc\"", null, .{}, ); defer free(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } { const parsed: [:0]const u8 = try fromSlice( [:0]const u8, gpa, "\\\\abc", null, .{}, ); defer free(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } } // Other value terminated slices { { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Expecting string literal, getting something else { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]const u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected string\n", "{}", .{status}); } // Expecting string literal, getting an incompatible tuple { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]const u8, gpa, ".{false}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{}", .{status}); } // Invalid string literal { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), ); try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{}", .{status}); } // Slice wrong child type { { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]const i8, gpa, "\"a\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]const i8, gpa, "\\\\a", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Bad alignment { { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } // Multi line strings inline for (.{ []const u8, [:0]const u8 }) |String| { // Nested { const S = struct { message: String, message2: String, message3: String, }; const parsed = try fromSlice(S, gpa, \\.{ \\ .message = \\ \\hello, world! \\ \\ \\this is a multiline string! \\ \\ \\ \\... \\ \\ , \\ .message2 = \\ \\this too...sort of. \\ , \\ .message3 = \\ \\ \\ \\and this. \\} , null, .{}); defer free(gpa, parsed); try std.testing.expectEqualStrings( "hello, world!\nthis is a multiline string!\n\n...", parsed.message, ); try std.testing.expectEqualStrings("this too...sort of.", parsed.message2); try std.testing.expectEqualStrings("\nand this.", parsed.message3); } } } test "std.zon enum literals" { const gpa = std.testing.allocator; const Enum = enum { foo, bar, baz, @"ab\nc", }; // Tags that exist try std.testing.expectEqual(Enum.foo, try fromSlice(Enum, gpa, ".foo", null, .{})); try std.testing.expectEqual(Enum.bar, try fromSlice(Enum, gpa, ".bar", null, .{})); try std.testing.expectEqual(Enum.baz, try fromSlice(Enum, gpa, ".baz", null, .{})); try std.testing.expectEqual( Enum.@"ab\nc", try fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), ); // Bad tag { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Enum, gpa, ".qux", &status, .{}), ); try std.testing.expectFmt( \\1:2: error: unexpected enum literal 'qux' \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' \\ , "{}", .{status}, ); } // Bad tag that's too long for parser { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), ); try std.testing.expectFmt( \\1:2: error: unexpected enum literal 'foobarbaz' \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' \\ , "{}", .{status}, ); } // Bad type { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Enum, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status}); } // Test embedded nulls in an identifier { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), ); try std.testing.expectFmt( "1:2: error: identifier cannot contain null bytes\n", "{}", .{status}, ); } } test "std.zon parse bool" { const gpa = std.testing.allocator; // Correct floats try std.testing.expectEqual(true, try fromSlice(bool, gpa, "true", null, .{})); try std.testing.expectEqual(false, try fromSlice(bool, gpa, "false", null, .{})); // Errors { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(bool, gpa, " foo", &status, .{}), ); try std.testing.expectFmt( \\1:2: error: invalid expression \\1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' \\1:2: note: precede identifier with '.' for an enum literal \\ , "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(bool, gpa, "123", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{}", .{status}); } } test "std.zon intFromFloatExact" { // Valid conversions try std.testing.expectEqual(@as(u8, 10), intFromFloatExact(u8, @as(f32, 10.0)).?); try std.testing.expectEqual(@as(i8, -123), intFromFloatExact(i8, @as(f64, @as(f64, -123.0))).?); try std.testing.expectEqual(@as(i16, 45), intFromFloatExact(i16, @as(f128, @as(f128, 45.0))).?); // Out of range try std.testing.expectEqual(@as(?u4, null), intFromFloatExact(u4, @as(f32, 16.0))); try std.testing.expectEqual(@as(?i4, null), intFromFloatExact(i4, @as(f64, -17.0))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f128, -2.0))); // Not a whole number try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f32, 0.5))); try std.testing.expectEqual(@as(?i8, null), intFromFloatExact(i8, @as(f64, 0.01))); // Infinity and NaN try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.inf(f32))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, -std.math.inf(f32))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.nan(f32))); } test "std.zon parse int" { const gpa = std.testing.allocator; // Test various numbers and types try std.testing.expectEqual(@as(u8, 10), try fromSlice(u8, gpa, "10", null, .{})); try std.testing.expectEqual(@as(i16, 24), try fromSlice(i16, gpa, "24", null, .{})); try std.testing.expectEqual(@as(i14, -4), try fromSlice(i14, gpa, "-4", null, .{})); try std.testing.expectEqual(@as(i32, -123), try fromSlice(i32, gpa, "-123", null, .{})); // Test limits try std.testing.expectEqual(@as(i8, 127), try fromSlice(i8, gpa, "127", null, .{})); try std.testing.expectEqual(@as(i8, -128), try fromSlice(i8, gpa, "-128", null, .{})); // Test characters try std.testing.expectEqual(@as(u8, 'a'), try fromSlice(u8, gpa, "'a'", null, .{})); try std.testing.expectEqual(@as(u8, 'z'), try fromSlice(u8, gpa, "'z'", null, .{})); // Test big integers try std.testing.expectEqual( @as(u65, 36893488147419103231), try fromSlice(u65, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(u65, 36893488147419103231), try fromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), ); // Test big integer limits try std.testing.expectEqual( @as(i66, 36893488147419103231), try fromSlice(i66, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(i66, -36893488147419103232), try fromSlice(i66, gpa, "-36893488147419103232", null, .{}), ); { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice( i66, gpa, "36893488147419103232", &status, .{}, )); try std.testing.expectFmt( "1:1: error: type 'i66' cannot represent value\n", "{}", .{status}, ); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice( i66, gpa, "-36893488147419103233", &status, .{}, )); try std.testing.expectFmt( "1:1: error: type 'i66' cannot represent value\n", "{}", .{status}, ); } // Test parsing whole number floats as integers try std.testing.expectEqual(@as(i8, -1), try fromSlice(i8, gpa, "-1.0", null, .{})); try std.testing.expectEqual(@as(i8, 123), try fromSlice(i8, gpa, "123.0", null, .{})); // Test non-decimal integers try std.testing.expectEqual(@as(i16, 0xff), try fromSlice(i16, gpa, "0xff", null, .{})); try std.testing.expectEqual(@as(i16, -0xff), try fromSlice(i16, gpa, "-0xff", null, .{})); try std.testing.expectEqual(@as(i16, 0o77), try fromSlice(i16, gpa, "0o77", null, .{})); try std.testing.expectEqual(@as(i16, -0o77), try fromSlice(i16, gpa, "-0o77", null, .{})); try std.testing.expectEqual(@as(i16, 0b11), try fromSlice(i16, gpa, "0b11", null, .{})); try std.testing.expectEqual(@as(i16, -0b11), try fromSlice(i16, gpa, "-0b11", null, .{})); // Test non-decimal big integers try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0x1ffffffffffffffff", null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0x1ffffffffffffffff", null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0x1ffffffffffffffff", null, .{}, )); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0o3777777777777777777777", null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0o3777777777777777777777", null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0o3777777777777777777777", null, .{}, )); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0b11111111111111111111111111111111111111111111111111111111111111111", null, .{}, )); // Number with invalid character in the middle { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "32a32", &status, .{})); try std.testing.expectFmt( "1:3: error: invalid digit 'a' for decimal base\n", "{}", .{status}, ); } // Failing to parse as int { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "true", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{}", .{status}); } // Failing because an int is out of range { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "256", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", .{status}, ); } // Failing because a negative int is out of range { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-129", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'i8' cannot represent value\n", "{}", .{status}, ); } // Failing because an unsigned int is negative { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", .{status}, ); } // Failing because a float is non-whole { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "1.5", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", .{status}, ); } // Failing because a float is negative { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1.0", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", .{status}, ); } // Negative integer zero { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-0", &status, .{})); try std.testing.expectFmt( \\1:2: error: integer literal '-0' is ambiguous \\1:2: note: use '0' for an integer zero \\1:2: note: use '-0.0' for a floating-point signed zero \\ , "{}", .{status}); } // Negative integer zero casted to float { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-0", &status, .{})); try std.testing.expectFmt( \\1:2: error: integer literal '-0' is ambiguous \\1:2: note: use '0' for an integer zero \\1:2: note: use '-0.0' for a floating-point signed zero \\ , "{}", .{status}); } // Negative float 0 is allowed try std.testing.expect( std.math.isNegativeZero(try fromSlice(f32, gpa, "-0.0", null, .{})), ); try std.testing.expect(std.math.isPositiveZero(try fromSlice(f32, gpa, "0.0", null, .{}))); // Double negation is not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "--2", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(f32, gpa, "--2.0", &status, .{}), ); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } // Invalid int literal { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0xg", &status, .{})); try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base\n", "{}", .{status}); } // Notes on invalid int literal { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0123", &status, .{})); try std.testing.expectFmt( \\1:1: error: number '0123' has leading zero \\1:1: note: use '0o' prefix for octal literals \\ , "{}", .{status}); } } test "std.zon negative char" { const gpa = std.testing.allocator; { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-'a'", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i16, gpa, "-'a'", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } } test "std.zon parse float" { const gpa = std.testing.allocator; // Test decimals try std.testing.expectEqual(@as(f16, 0.5), try fromSlice(f16, gpa, "0.5", null, .{})); try std.testing.expectEqual( @as(f32, 123.456), try fromSlice(f32, gpa, "123.456", null, .{}), ); try std.testing.expectEqual( @as(f64, -123.456), try fromSlice(f64, gpa, "-123.456", null, .{}), ); try std.testing.expectEqual(@as(f128, 42.5), try fromSlice(f128, gpa, "42.5", null, .{})); // Test whole numbers with and without decimals try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5.0", null, .{})); try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5", null, .{})); try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102.0", null, .{})); try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102", null, .{})); // Test characters and negated characters try std.testing.expectEqual(@as(f32, 'a'), try fromSlice(f32, gpa, "'a'", null, .{})); try std.testing.expectEqual(@as(f32, 'z'), try fromSlice(f32, gpa, "'z'", null, .{})); // Test big integers try std.testing.expectEqual( @as(f32, 36893488147419103231), try fromSlice(f32, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(f32, -36893488147419103231), try fromSlice(f32, gpa, "-36893488147419103231", null, .{}), ); try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice( f128, gpa, "0x1ffffffffffffffff", null, .{}, )); try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice( f32, gpa, "0x1ffffffffffffffff", null, .{}, )); // Exponents, underscores try std.testing.expectEqual( @as(f32, 123.0E+77), try fromSlice(f32, gpa, "12_3.0E+77", null, .{}), ); // Hexadecimal try std.testing.expectEqual( @as(f32, 0x103.70p-5), try fromSlice(f32, gpa, "0x103.70p-5", null, .{}), ); try std.testing.expectEqual( @as(f32, -0x103.70), try fromSlice(f32, gpa, "-0x103.70", null, .{}), ); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), try fromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), ); // inf, nan try std.testing.expect(std.math.isPositiveInf(try fromSlice(f32, gpa, "inf", null, .{}))); try std.testing.expect(std.math.isNegativeInf(try fromSlice(f32, gpa, "-inf", null, .{}))); try std.testing.expect(std.math.isNan(try fromSlice(f32, gpa, "nan", null, .{}))); // Negative nan not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-nan", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } // nan as int not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // nan as int not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // inf as int not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "inf", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // -inf as int not allowed { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-inf", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // Bad identifier as float { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "foo", &status, .{})); try std.testing.expectFmt( \\1:1: error: invalid expression \\1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' \\1:1: note: precede identifier with '.' for an enum literal \\ , "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-foo", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}, ); } // Non float as float { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(f32, gpa, "\"foo\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status}); } } test "std.zon free on error" { // Test freeing partially allocated structs { const Struct = struct { x: []const u8, y: []const u8, z: bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\ .y = "world", \\ .z = "fail", \\} , null, .{})); } // Test freeing partially allocated tuples { const Struct = struct { []const u8, []const u8, bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ "hello", \\ "world", \\ "fail", \\} , null, .{})); } // Test freeing structs with missing fields { const Struct = struct { x: []const u8, y: bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\} , null, .{})); } // Test freeing partially allocated arrays { try std.testing.expectError(error.ParseZon, fromSlice( [3][]const u8, std.testing.allocator, \\.{ \\ "hello", \\ false, \\ false, \\} , null, .{}, )); } // Test freeing partially allocated slices { try std.testing.expectError(error.ParseZon, fromSlice( [][]const u8, std.testing.allocator, \\.{ \\ "hello", \\ "world", \\ false, \\} , null, .{}, )); } // We can parse types that can't be freed, as long as they contain no allocations, e.g. untagged // unions. try std.testing.expectEqual( @as(f32, 1.5), (try fromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", null, .{})).x, ); // We can also parse types that can't be freed if it's impossible for an error to occur after // the allocation, as is the case here. { const result = try fromSlice( union { x: []const u8 }, std.testing.allocator, ".{ .x = \"foo\" }", null, .{}, ); defer free(std.testing.allocator, result.x); try std.testing.expectEqualStrings("foo", result.x); } // However, if it's possible we could get an error requiring we free the value, but the value // cannot be freed (e.g. untagged unions) then we need to turn off `free_on_error` for it to // compile. { const S = struct { union { x: []const u8 }, bool, }; const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", null, .{ .free_on_error = false }, ); defer free(std.testing.allocator, result[0].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expect(result[1]); } // Again but for structs. { const S = struct { a: union { x: []const u8 }, b: bool, }; const result = try fromSlice( S, std.testing.allocator, ".{ .a = .{ .x = \"foo\" }, .b = true }", null, .{ .free_on_error = false, }, ); defer free(std.testing.allocator, result.a.x); try std.testing.expectEqualStrings("foo", result.a.x); try std.testing.expect(result.b); } // Again but for arrays. { const S = [2]union { x: []const u8 }; const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", null, .{ .free_on_error = false, }, ); defer free(std.testing.allocator, result[0].x); defer free(std.testing.allocator, result[1].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expectEqualStrings("bar", result[1].x); } // Again but for slices. { const S = []union { x: []const u8 }; const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", null, .{ .free_on_error = false, }, ); defer std.testing.allocator.free(result); defer free(std.testing.allocator, result[0].x); defer free(std.testing.allocator, result[1].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expectEqualStrings("bar", result[1].x); } } test "std.zon vector" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15329 const gpa = std.testing.allocator; // Passing cases try std.testing.expectEqual( @Vector(0, bool){}, try fromSlice(@Vector(0, bool), gpa, ".{}", null, .{}), ); try std.testing.expectEqual( @Vector(3, bool){ true, false, true }, try fromSlice(@Vector(3, bool), gpa, ".{true, false, true}", null, .{}), ); try std.testing.expectEqual( @Vector(0, f32){}, try fromSlice(@Vector(0, f32), gpa, ".{}", null, .{}), ); try std.testing.expectEqual( @Vector(3, f32){ 1.5, 2.5, 3.5 }, try fromSlice(@Vector(3, f32), gpa, ".{1.5, 2.5, 3.5}", null, .{}), ); try std.testing.expectEqual( @Vector(0, u8){}, try fromSlice(@Vector(0, u8), gpa, ".{}", null, .{}), ); try std.testing.expectEqual( @Vector(3, u8){ 2, 4, 6 }, try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}), ); { try std.testing.expectEqual( @Vector(0, *const u8){}, try fromSlice(@Vector(0, *const u8), gpa, ".{}", null, .{}), ); const pointers = try fromSlice(@Vector(3, *const u8), gpa, ".{2, 4, 6}", null, .{}); defer free(gpa, pointers); try std.testing.expectEqualDeep(@Vector(3, *const u8){ &2, &4, &6 }, pointers); } { try std.testing.expectEqual( @Vector(0, ?*const u8){}, try fromSlice(@Vector(0, ?*const u8), gpa, ".{}", null, .{}), ); const pointers = try fromSlice(@Vector(3, ?*const u8), gpa, ".{2, null, 6}", null, .{}); defer free(gpa, pointers); try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers); } // Too few fields { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(@Vector(2, f32), gpa, ".{0.5}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: expected 2 vector elements; found 1\n", "{}", .{status}, ); } // Too many fields { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(@Vector(2, f32), gpa, ".{0.5, 1.5, 2.5}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: expected 2 vector elements; found 3\n", "{}", .{status}, ); } // Wrong type fields { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(@Vector(3, f32), gpa, ".{0.5, true, 2.5}", &status, .{}), ); try std.testing.expectFmt( "1:8: error: expected type 'f32'\n", "{}", .{status}, ); } // Wrong type { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(@Vector(3, u8), gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{}", .{status}); } // Elements should get freed on error { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(@Vector(3, *u8), gpa, ".{1, true, 3}", &status, .{}), ); try std.testing.expectFmt("1:6: error: expected type 'u8'\n", "{}", .{status}); } } test "std.zon add pointers" { const gpa = std.testing.allocator; // Primitive with varying levels of pointers { const result = try fromSlice(*u32, gpa, "10", null, .{}); defer free(gpa, result); try std.testing.expectEqual(@as(u32, 10), result.*); } { const result = try fromSlice(**u32, gpa, "10", null, .{}); defer free(gpa, result); try std.testing.expectEqual(@as(u32, 10), result.*.*); } { const result = try fromSlice(***u32, gpa, "10", null, .{}); defer free(gpa, result); try std.testing.expectEqual(@as(u32, 10), result.*.*.*); } // Primitive optional with varying levels of pointers { const some = try fromSlice(?*u32, gpa, "10", null, .{}); defer free(gpa, some); try std.testing.expectEqual(@as(u32, 10), some.?.*); const none = try fromSlice(?*u32, gpa, "null", null, .{}); defer free(gpa, none); try std.testing.expectEqual(null, none); } { const some = try fromSlice(*?u32, gpa, "10", null, .{}); defer free(gpa, some); try std.testing.expectEqual(@as(u32, 10), some.*.?); const none = try fromSlice(*?u32, gpa, "null", null, .{}); defer free(gpa, none); try std.testing.expectEqual(null, none.*); } { const some = try fromSlice(?**u32, gpa, "10", null, .{}); defer free(gpa, some); try std.testing.expectEqual(@as(u32, 10), some.?.*.*); const none = try fromSlice(?**u32, gpa, "null", null, .{}); defer free(gpa, none); try std.testing.expectEqual(null, none); } { const some = try fromSlice(*?*u32, gpa, "10", null, .{}); defer free(gpa, some); try std.testing.expectEqual(@as(u32, 10), some.*.?.*); const none = try fromSlice(*?*u32, gpa, "null", null, .{}); defer free(gpa, none); try std.testing.expectEqual(null, none.*); } { const some = try fromSlice(**?u32, gpa, "10", null, .{}); defer free(gpa, some); try std.testing.expectEqual(@as(u32, 10), some.*.*.?); const none = try fromSlice(**?u32, gpa, "null", null, .{}); defer free(gpa, none); try std.testing.expectEqual(null, none.*.*); } // Pointer to an array { const result = try fromSlice(*[3]u8, gpa, ".{ 1, 2, 3 }", null, .{}); defer free(gpa, result); try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*); } // A complicated type with nested internal pointers and string allocations { const Inner = struct { f1: *const ?*const []const u8, f2: *const ?*const []const u8, }; const Outer = struct { f1: *const ?*const Inner, f2: *const ?*const Inner, }; const expected: Outer = .{ .f1 = &&.{ .f1 = &null, .f2 = &&"foo", }, .f2 = &null, }; const found = try fromSlice(?*Outer, gpa, \\.{ \\ .f1 = .{ \\ .f1 = null, \\ .f2 = "foo", \\ }, \\ .f2 = null, \\} , null, .{}); defer free(gpa, found); try std.testing.expectEqualDeep(expected, found.?.*); } // Test that optional types are flattened correctly in errors { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type '?u8'\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const f32, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type '?f32'\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const @Vector(3, u8), gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type '?@Vector(3, u8)'\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const bool, gpa, "10", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type '?bool'\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const struct { a: i32 }, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional struct\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const struct { i32 }, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional tuple\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const union { x: void }, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional union\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const [3]u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(?[3]u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const []u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(?[]u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const []const u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional string\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const enum { foo }, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected optional enum literal\n", "{}", .{status}); } } |
Generated by zstd-live on 2025-08-13 02:35:12 UTC. |