Files
mlarouche cb7a773ab8 ZON: Fix parsing from a specific node with fromZoir() (#37193)
Fix parsing a specific node with fromZoir() method by passing the wanted node to the inner functions.

Also added a test to make sure the use case gets tested.

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37193
Reviewed-by: Mason Remaley <masonremaley@noreply.codeberg.org>
2026-10-15 15:07:27 +02:00

4902 lines
157 KiB
Zig

//! Use `fromSlice`/`fromSliceNoAlloc` to parse a ZON string into a Zig value. Human readable
//! errors are written to an out parameter.
//!
//! If your data is already available in tree form, consider `fromZoir`/`fromZoirNoAlloc`.
//!
//! To parse into an existing value, see the `updateFrom*` variants. For lower level control over
//! parsing, see `std.zig.ZonGen`. For importing ZON at compile time, use `@import`.
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 ArrayList = std.ArrayList;
const ArenaAllocator = std.heap.ArenaAllocator;
/// Rename when adding or removing support for a type.
const valid_types = {};
/// Errors encountered while parsing ZON. See `log` and `fatal` for reporting errors to the user.
pub const Diagnostics = struct {
errors: []const Error,
/// Log the failure with `std.log.err`.
pub fn log(self: *const Diagnostics, path: []const u8) void {
std.log.err("{f}", .{self.fmt(path)});
}
test log {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const MyType = struct {
foo: u32,
bar: u32,
};
const parsed: MyType = fromSliceNoAlloc(MyType, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = 1, .bar = 2 }",
.diagnostics = &diagnostics,
}) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ParseZon => b: {
diagnostics.log("input.zon");
break :b .{ .foo = 0, .bar = 0 };
},
};
_ = parsed;
}
/// Log the failure with `std.log.err`, and then terminate the process with exit code 1.
pub fn fatal(self: *const Diagnostics, path: []const u8) noreturn {
std.process.fatal("{f}", .{self.fmt(path)});
}
test fatal {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const MyType = struct {
foo: u32,
bar: u32,
};
const parsed = fromSliceNoAlloc(MyType, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = 1, .bar = 2 }",
.diagnostics = &diagnostics,
}) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ParseZon => diagnostics.fatal("input.zon"),
};
_ = parsed;
}
/// Formats a human readable description of any errors.
pub fn fmt(self: *const Diagnostics, path: []const u8) Formatter {
return .{
.errors = self.errors,
.path = path,
};
}
fn astLoc(
ast: *const Ast,
maybe_token: Ast.OptionalTokenIndex,
node_or_offset: u32,
) Ast.Location {
if (maybe_token.unwrap()) |token| {
var loc = ast.tokenLocation(0, token);
loc.column += node_or_offset;
return loc;
} else {
const ast_node: Ast.Node.Index = @fromBackingInt(@intCast(node_or_offset));
const token = ast.nodeMainToken(ast_node);
return ast.tokenLocation(0, token);
}
}
pub const Formatter = struct {
path: []const u8,
errors: []const Error,
pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
for (self.errors, 0..) |e, i| {
if (i > 0) try w.writeByte('\n');
try w.print("{s}:{d}:{d}: error: {s}", .{
self.path,
e.loc.line + 1,
e.loc.column + 1,
e.msg,
});
for (e.notes) |note| {
try w.print("\n{s}:{d}:{d}: note: {s}", .{
self.path,
note.loc.line + 1,
note.loc.column + 1,
note.msg,
});
}
}
}
};
pub const Error = struct {
msg: []const u8,
loc: Ast.Location,
token: Ast.OptionalTokenIndex,
/// If `token == .none`, this is an `Ast.Node.Index`.
/// Otherwise, this is a byte offset into `token`.
node_or_offset: u32,
notes: []Note,
const Options = struct {
msg: []const u8,
token: Ast.OptionalTokenIndex,
node_or_offset: u32,
notes: []Note,
};
fn init(ast: *const Ast, options: Error.Options) Error {
return .{
.msg = options.msg,
.loc = astLoc(ast, options.token, options.node_or_offset),
.token = options.token,
.node_or_offset = options.node_or_offset,
.notes = options.notes,
};
}
};
pub const Note = struct {
msg: []const u8,
loc: Ast.Location,
token: Ast.OptionalTokenIndex,
/// If `token == .none`, this is an `Ast.Node.Index`.
/// Otherwise, this is a byte offset into `token`.
node_or_offset: u32,
const Options = struct {
msg: []const u8,
token: Ast.OptionalTokenIndex,
node_or_offset: u32,
};
fn init(ast: *const Ast, options: Note.Options) @This() {
return .{
.msg = options.msg,
.loc = astLoc(ast, options.token, options.node_or_offset),
.token = options.token,
.node_or_offset = options.node_or_offset,
};
}
};
};
pub const Options = struct {
/// Used for scratch allocations.
gpa: Allocator,
/// Used for allocating diagnostics and results.
arena: Allocator,
/// The ZON source to parse.
source: [:0]const u8,
/// Initialized by the callee with a human readable description of any parse errors that
/// occured.
diagnostics: *Diagnostics,
/// If true, unknown fields do not error.
ignore_unknown_fields: bool = false,
};
/// Parses the given slice as ZON, writing any errors to `options.diagnostics`.
pub fn fromSlice(T: type, options: Options) error{ OutOfMemory, ParseZon }!T {
var value: T = undefined;
try fromSliceInner(T, &value, false, options);
return value;
}
test fromSlice {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const TextureOptions = struct {
const AddressMode = enum { clamp, reflect, wrap, zero };
address_mode: struct { u: AddressMode = .wrap, v: AddressMode = .wrap },
mipmaps: bool = true,
premultiply: bool = true,
};
const source =
\\.{
\\ .address_mode = .{ .u = .clamp },
\\ .mipmaps = false,
\\}
;
var diagnostics: Diagnostics = undefined;
const options = fromSlice(TextureOptions, .{
.gpa = gpa,
.arena = arena,
.source = source,
.diagnostics = &diagnostics,
}) catch |err| switch (err) {
error.OutOfMemory => return err,
error.ParseZon => {
diagnostics.log("texture_options.zon");
return err;
},
};
try std.testing.expectEqualDeep(TextureOptions{
.address_mode = .{ .u = .clamp, .v = .wrap },
.mipmaps = false,
.premultiply = true,
}, options);
}
/// Like `fromSlice` but the result type may not contain pointers, allowing it to outlive
/// `options.arena`.
pub fn fromSliceNoAlloc(T: type, options: Options) error{ OutOfMemory, ParseZon }!T {
comptime assert(!requiresAllocator(T));
return fromSlice(T, options);
}
/// Similar to `fromSlice`, but updates the existing contents of `value` in place. Fields
/// unspecified by ZON are left unchanged.
///
/// Assumes that `value` points to a fully initialized value prior to this call. Slices and untagged
/// unions are replaced in their entirey when specified. On error, `value` remains valid, but may be
/// partially updated.
pub fn updateFromSlice(
T: type,
value: *T,
options: Options,
) error{ OutOfMemory, ParseZon }!void {
try fromSliceInner(T, value, true, options);
}
test updateFromSlice {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const MyTextEditorConfig = struct {
indentation: enum { spaces, tabs },
columns: []const u16,
dark_mode: enum { auto, dark, light },
theme: []const u8,
};
const global_config =
\\.{
\\ .indentation = .spaces,
\\ .columns = .{ 100 },
\\ .dark_mode = .dark,
\\}
;
const project_config =
\\.{
\\ .indentation = .tabs,
\\}
;
var config: MyTextEditorConfig = .{
.indentation = .spaces,
.columns = &.{},
.dark_mode = .auto,
.theme = "default",
};
var diagnostics: Diagnostics = undefined;
updateFromSlice(MyTextEditorConfig, &config, .{
.gpa = gpa,
.arena = arena,
.source = global_config,
.diagnostics = &diagnostics,
}) catch |err| switch (err) {
error.OutOfMemory => return err,
error.ParseZon => {
diagnostics.log("global_config.zon");
return err;
},
};
updateFromSlice(MyTextEditorConfig, &config, .{
.gpa = gpa,
.arena = arena,
.source = project_config,
.diagnostics = &diagnostics,
}) catch |err| switch (err) {
error.OutOfMemory => return err,
error.ParseZon => {
diagnostics.log("project_config.zon");
return err;
},
};
try std.testing.expectEqualDeep(MyTextEditorConfig{
.indentation = .tabs,
.columns = &.{100},
.dark_mode = .dark,
.theme = "default",
}, config);
}
/// Like `updateFromSlice` but the `T` may not contain pointers, allowing `value` to outlive
/// `options.arena`.
pub fn updateFromSliceNoAlloc(
T: type,
value: *T,
options: Options,
) error{ OutOfMemory, ParseZon }!void {
comptime assert(!requiresAllocator(T));
try fromSliceInner(T, value, true, options);
}
fn fromSliceInner(
T: type,
value: *T,
initialized: bool,
options: Options,
) error{ OutOfMemory, ParseZon }!void {
var errors: std.ArrayList(Diagnostics.Error) = .empty;
defer options.diagnostics.* = .{ .errors = errors.items };
var ast = try std.zig.Ast.parse(options.gpa, options.source, .{ .mode = .zon });
defer ast.deinit(options.gpa);
var zoir = try ZonGen.generate(options.gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(options.gpa);
try inner(T, value, initialized, .{
.arena = options.arena,
.ast = &ast,
.zoir = &zoir,
.node = .root,
.errors = &errors,
.ignore_unknown_fields = options.ignore_unknown_fields,
});
}
pub const FromZoirOptions = struct {
/// Used for allocating diagnostics and results.
arena: Allocator,
/// The AST for this ZON value.
ast: *const Ast,
/// The zig object intermediate representation of this ZON value.
zoir: *const Zoir,
/// The node to start parsing at.
node: Zoir.Node.Index = .root,
/// Initialized by the callee with a human readable description of any parse errors that
/// occured.
diagnostics: *Diagnostics,
/// If true, unknown fields do not error.
ignore_unknown_fields: bool = false,
};
/// Like `fromSlice`, but operates on `Zoir` instead of ZON source.
pub fn fromZoir(T: type, options: FromZoirOptions) error{ OutOfMemory, ParseZon }!T {
var value: T = undefined;
try fromZoirInner(T, &value, false, options);
return value;
}
/// Like `fromSliceNoAlloc`, but operates on `Zoir` instead of ZON source.
pub fn fromZoirNoAlloc(T: type, options: FromZoirOptions) error{ OutOfMemory, ParseZon }!T {
comptime assert(!requiresAllocator(T));
return fromZoir(T, options);
}
/// Like `updateFromSlice`, but operates on `Zoir` instead of ZON source.
pub fn updateFromZoir(
T: type,
value: *T,
options: FromZoirOptions,
) error{ OutOfMemory, ParseZon }!void {
return fromZoirInner(T, value, true, options);
}
/// Like `updateFromSliceNoAlloc`, but operates on `Zoir` instead of ZON source.
pub fn updateFromZoirNoAlloc(
T: type,
value: *T,
options: FromZoirOptions,
) error{ OutOfMemory, ParseZon }!void {
comptime assert(!requiresAllocator(T));
return updateFromZoir(T, value, options);
}
fn fromZoirInner(
T: type,
value: *T,
initialized: bool,
options: FromZoirOptions,
) error{ OutOfMemory, ParseZon }!void {
var errors: std.ArrayList(Diagnostics.Error) = .empty;
defer options.diagnostics.* = .{ .errors = errors.items };
return inner(T, value, initialized, .{
.arena = options.arena,
.ast = options.ast,
.zoir = options.zoir,
.node = options.node,
.errors = &errors,
.ignore_unknown_fields = options.ignore_unknown_fields,
});
}
const InnerOptions = struct {
arena: Allocator,
ast: *const Ast,
zoir: *const Zoir,
node: Zoir.Node.Index = .root,
errors: *std.ArrayList(Diagnostics.Error),
ignore_unknown_fields: bool,
};
fn inner(
T: type,
value: *T,
initialized: bool,
options: InnerOptions,
) error{ OutOfMemory, ParseZon }!void {
comptime assert(canParseType(T));
if (options.zoir.hasCompileErrors()) {
for (options.zoir.compile_errors) |e| {
var notes: std.ArrayList(Diagnostics.Note) = .empty;
for (e.getNotes(options.zoir)) |note| {
try notes.append(options.arena, .init(options.ast, .{
.msg = try options.arena.dupe(u8, note.msg.get(options.zoir)),
.token = note.token,
.node_or_offset = note.node_or_offset,
}));
}
try options.errors.append(options.arena, .init(options.ast, .{
.msg = try options.arena.dupe(u8, e.msg.get(options.zoir)),
.token = e.token,
.node_or_offset = e.node_or_offset,
.notes = notes.items,
}));
}
return error.ParseZon;
}
var parser: Parser = .{
.arena = options.arena,
.ast = options.ast,
.zoir = options.zoir,
.ignore_unknown_fields = options.ignore_unknown_fields,
.errors = options.errors,
};
try parser.parseExprInto(options.node, value, initialized);
}
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".field_types) |field_type| {
if (requiresAllocator(field_type)) {
break true;
}
} else false,
.@"union" => |@"union"| inline for (@"union".field_types) |field_type| {
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 {
arena: Allocator,
ast: *const Ast,
zoir: *const Zoir,
errors: *std.ArrayList(Diagnostics.Error),
ignore_unknown_fields: bool,
const ParseExprError = error{ ParseZon, OutOfMemory };
fn parseExprInto(
self: *Parser,
node: Zoir.Node.Index,
out: anytype,
initialized: bool,
) ParseExprError!void {
return self.parseExprIntoInner(node, out, initialized) catch |err| switch (err) {
error.WrongType => return self.failExpectedType(@TypeOf(out.*), node),
else => |e| return e,
};
}
const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType };
fn parseExprIntoInner(
self: *@This(),
node: Zoir.Node.Index,
out: anytype,
initialized: bool,
) ParseExprInnerError!void {
if (@TypeOf(out.*) == Zoir.Node.Index) {
out.* = node;
return;
}
switch (@typeInfo(@TypeOf(out.*))) {
.optional => |optional| if (node.get(self.zoir) == .null) {
out.* = null;
} else if (initialized and out.* != null) {
try self.parseExprIntoInner(node, &out.*.?, true);
} else {
var some: optional.child = undefined;
try self.parseExprIntoInner(node, &some, false);
out.* = some;
},
.bool => out.* = try self.parseBool(node),
.int => out.* = try self.parseInt(@TypeOf(out.*), node),
.float => out.* = try self.parseFloat(@TypeOf(out.*), node),
.@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node),
.pointer => |pointer| switch (pointer.size) {
.one => {
// Get a pointer to update
const new = b: {
// If the existing value is initialized and non const, use it
if (initialized and !pointer.attrs.@"const") break :b out.*;
// Otherwise, allocate new memory
const new = try self.arena.alignedCreate(
pointer.child,
if (pointer.attrs.@"align") |a| .fromByteUnits(a) else null,
);
// If the original value was initialized, dupe it into the new memory
if (initialized) new.* = out.*.*;
break :b new;
};
try self.parseExprIntoInner(
node,
new,
initialized,
);
out.* = new;
},
.slice => {
// Slices are replaced wholesale since ZON doesn't have syntax for specifying
// just a part of a slice.
const new = b: {
const new = try self.parseSlicePointer(@TypeOf(out.*), node);
break :b new;
};
out.* = new;
},
else => comptime unreachable,
},
.array => try self.parseArrayInto(node, out),
.vector => try self.parseVectorInto(node, out),
.@"struct" => |@"struct"| if (@"struct".is_tuple)
try self.parseTupleInto(node, out)
else
try self.parseStructInto(node, out, initialized),
.@"union" => try self.parseUnionInto(node, out, initialized),
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: Parser,
T: type,
node: Zoir.Node.Index,
) error{ ParseZon, OutOfMemory } {
@branchHint(.cold);
return self.failExpectedTypeInner(T, false, node);
}
fn failExpectedTypeInner(
self: Parser,
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.attrs.@"const" and
(pointer.sentinel() == null or pointer.sentinel() == 0) and
(pointer.attrs.@"align" == null or pointer.attrs.@"align" == 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: Parser, node: Zoir.Node.Index) !bool {
switch (node.get(self.zoir)) {
.true => return true,
.false => return false,
else => return error.WrongType,
}
}
fn parseInt(self: Parser, 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: Parser, 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, .nearest_even)[0],
},
.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: Parser, 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_info = @typeInfo(T).@"enum";
comptime var kvs_list: [enum_info.field_names.len]struct { []const u8, T } = undefined;
inline for (enum_info.field_names, enum_info.field_values, 0..) |enum_field_name, enum_field_value, i| {
kvs_list[i] = .{ enum_field_name, @fromBackingInt(@intCast(enum_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: *Parser, T: type, node: Zoir.Node.Index) ParseExprInnerError!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: *Parser, T: type, node: Zoir.Node.Index) ParseExprInnerError!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 aw: std.Io.Writer.Allocating = .init(self.arena);
try aw.ensureUnusedCapacity(size_hint);
defer aw.deinit();
const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory;
switch (result) {
.success => {},
.failure => |err| {
const token = self.ast.nodeMainToken(ast_node);
const raw_string = self.ast.tokenSlice(token);
return self.failTokenFmt(token, @intCast(err.offset()), "{f}", .{err.fmt(raw_string)});
},
}
if (pointer.child != u8 or
pointer.size != .slice or
!pointer.attrs.@"const" or
(pointer.sentinel() != null and pointer.sentinel() != 0) or
(pointer.attrs.@"align" != null and pointer.attrs.@"align" != 1))
{
return error.WrongType;
}
if (pointer.sentinel() != null) {
return aw.toOwnedSliceSentinel(0);
} else {
return aw.toOwnedSlice();
}
}
fn parseSlice(self: *Parser, 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.arena.allocWithOptions(
pointer.child,
nodes.len,
.fromByteUnitsOptional(pointer.attrs.@"align"),
pointer.sentinel(),
);
// Parse the elements and return the slice
for (slice, 0..) |*elem, i| {
try self.parseExprInto(nodes.at(@intCast(i)), elem, false);
}
return slice;
}
fn parseArrayInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void {
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 = @typeInfo(@TypeOf(out.*)).array;
// Check if the size matches
if (nodes.len < array.len) {
return self.failNodeFmt(
node,
"expected {} array elements; found {}",
.{ array.len, nodes.len },
);
} else if (nodes.len > array.len) {
return self.failNodeFmt(
nodes.at(array.len),
"index {} outside of array of length {}",
.{ array.len, array.len },
);
}
// Parse the elements and return the array
for (out, 0..) |*elem, i| {
try self.parseExprInto(nodes.at(@intCast(i)), elem, false);
}
if (array.sentinel()) |s| {
out[array.len] = s;
}
}
fn parseVectorInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void {
const vector = @typeInfo(@TypeOf(out.*)).vector;
var array: [vector.len]vector.child = undefined;
try self.parseArrayInto(node, &array);
out.* = array;
}
fn parseStructInto(
self: *Parser,
node: Zoir.Node.Index,
out: anytype,
initialized: bool,
) !void {
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 info = @typeInfo(@TypeOf(out.*)).@"struct";
// 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: [info.field_names.len]struct { []const u8, usize } = undefined;
for (&kvs_list, info.field_names, info.field_attrs, 0..) |*kv, field_name, field_attrs, i| {
kv.* = .{ field_name, if (field_attrs.@"comptime") comptime_field else i };
}
break :b .initComptime(kvs_list);
};
// Parse the struct
var field_found: [info.field_names.len]bool = @splat(false);
for (0..fields.names.len) |i| {
const name = fields.names[i].get(self.zoir);
const field_index = field_indices.get(name) orelse {
if (self.ignore_unknown_fields) continue;
return self.failUnexpected(@TypeOf(out.*), "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...(info.field_names.len - 1) => |j| {
if (info.field_attrs[j].@"comptime") unreachable;
try self.parseExprInto(
fields.vals.at(@intCast(i)),
&@field(out, info.field_names[j]),
initialized,
);
},
else => unreachable, // Can't be out of bounds
}
}
// Fill in any missing default fields
if (!initialized) {
inline for (field_found, 0..) |found, i| {
if (!found) {
const field_attrs = info.field_attrs[i];
if (field_attrs.defaultValue(info.field_types[i])) |default| {
@field(out, info.field_names[i]) = default;
} else {
return self.failNodeFmt(
node,
"missing required field {s}",
.{info.field_names[i]},
);
}
}
}
}
}
fn parseTupleInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void {
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 info = @typeInfo(@TypeOf(out.*)).@"struct";
if (nodes.len > info.field_names.len) {
return self.failNodeFmt(
nodes.at(info.field_names.len),
"index {} outside of tuple length {}",
.{ info.field_names.len, info.field_names.len },
);
}
inline for (0..info.field_names.len) |i| {
// Check if we're out of bounds
if (i >= nodes.len) {
if (info.field_attrs[i].defaultValue(info.field_types[i])) |default| {
@field(out.*, info.field_names[i]) = default;
} else {
return self.failNodeFmt(node, "missing tuple field with index {}", .{i});
}
} else {
if (info.field_attrs[i].@"comptime") {
return self.failComptimeField(node, i);
} else {
try self.parseExprInto(nodes.at(i), &out[i], false);
}
}
}
}
fn parseUnionInto(
self: *Parser,
node: Zoir.Node.Index,
out: anytype,
initialized: bool,
) !void {
const @"union" = @typeInfo(@TypeOf(out.*)).@"union";
if (@"union".field_names.len == 0) comptime unreachable;
// Gather info on the fields
const field_indices = b: {
comptime var kvs_list: [@"union".field_names.len]struct {
[]const u8,
usize,
} = undefined;
inline for (@"union".field_names, 0..) |field_name, 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(
@TypeOf(out.*),
"field",
node,
null,
field_name_str,
);
};
// Initialize the union from the given field.
switch (field_index) {
inline 0...@"union".field_names.len - 1 => |i| {
// Fail if the field is not void
if (@"union".field_types[i] != void)
return self.failNode(node, "expected union");
// Instantiate the union
out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], {});
},
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(@TypeOf(out.*), "field", node, 0, field_name_str);
switch (field_index) {
inline 0...@"union".field_names.len - 1 => |i| {
if (@"union".field_types[i] == void) {
return self.failNode(field_val, "expected type 'void'");
} else {
const field_initialized = b: {
if (!initialized) break :b false;
if (@"union".tag_type == null) break :b false;
const tag = @field(@"union".tag_type.?, @"union".field_names[i]);
if (out.* != tag) break :b false;
break :b true;
};
if (!field_initialized) {
out.* = @unionInit(
@TypeOf(out.*),
@"union".field_names[i],
undefined,
);
}
try self.parseExprInto(
field_val,
&@field(out.*, @"union".field_names[i]),
field_initialized,
);
}
},
else => unreachable, // Can't be out of bounds
}
},
else => return error.WrongType,
}
}
fn failTokenFmt(
self: Parser,
token: Ast.TokenIndex,
offset: u32,
comptime fmt: []const u8,
args: anytype,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
return self.failTokenFmtNote(token, offset, fmt, args, &.{});
}
fn failTokenFmtNote(
self: Parser,
token: Ast.TokenIndex,
offset: u32,
comptime fmt: []const u8,
args: anytype,
notes: []Diagnostics.Note,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
comptime assert(args.len > 0);
try self.errors.append(self.arena, .init(self.ast, .{
.token = .fromToken(token),
.node_or_offset = offset,
.msg = try std.fmt.allocPrint(self.arena, fmt, args),
.notes = notes,
}));
return error.ParseZon;
}
fn failNodeFmt(
self: Parser,
node: Zoir.Node.Index,
comptime fmt: []const u8,
args: anytype,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
const token = self.ast.nodeMainToken(node.getAstNode(self.zoir));
return self.failTokenFmt(token, 0, fmt, args);
}
fn failToken(
self: Parser,
failure: Diagnostics.Error,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
try self.errors.append(self.arena, failure);
return error.ParseZon;
}
fn failNode(
self: Parser,
node: Zoir.Node.Index,
msg: []const u8,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
const token = self.ast.nodeMainToken(node.getAstNode(self.zoir));
return self.failToken(.init(self.ast, .{
.token = .fromToken(token),
.node_or_offset = 0,
.msg = msg,
.notes = &.{},
}));
}
fn failCannotRepresent(
self: Parser,
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: Parser,
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 self.ast.nodeMainToken(node.getAstNode(self.zoir));
switch (@typeInfo(T)) {
inline .@"struct", .@"union", .@"enum" => |info| {
var notes: std.ArrayList(Diagnostics.Note) = .empty;
if (info.field_names.len == 0) {
try notes.append(self.arena, .init(self.ast, .{
.token = .fromToken(token),
.node_or_offset = 0,
.msg = "none expected",
}));
} else {
var aw: std.Io.Writer.Allocating = .init(self.arena);
aw.writer.writeAll("supported: ") catch return error.OutOfMemory;
inline for (info.field_names, 0..) |field_name, i| {
if (i != 0) aw.writer.writeAll(", ") catch return error.OutOfMemory;
aw.writer.print("'{f}'", .{std.zig.fmtIdFlags(field_name, .{
.allow_primitive = true,
.allow_underscore = true,
})}) catch return error.OutOfMemory;
}
try notes.append(self.arena, .init(self.ast, .{
.token = .fromToken(token),
.node_or_offset = 0,
.msg = try aw.toOwnedSlice(),
}));
}
return self.failTokenFmtNote(
token,
0,
"unexpected {s} '{s}'",
.{ item_kind, name },
notes.items,
);
},
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: Parser,
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(.init(self.ast, .{
.token = .fromToken(token),
.node_or_offset = 0,
.msg = "cannot initialize comptime field",
.notes = &.{},
}));
}
};
fn intFromFloatExact(T: type, value: anytype) ?T {
const max: @TypeOf(value) = @floatFromInt(std.math.maxInt(T));
const min: @TypeOf(value) = @floatFromInt(std.math.minInt(T));
if (value > max or value < min) {
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",
.spirv,
.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".field_types, @"struct".field_attrs) |field_type, field_attrs| {
if (!field_attrs.@"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".field_types) |field_type| {
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 arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(struct {}, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1 .y = 2}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:13: error: expected ',' after initializer
, "{f}", .{diagnostics.fmt("input.zon")});
}
test "std.zon comments" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
try std.testing.expectEqual(@as(u8, 10), fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source =
\\// comment
\\10 // comment
\\// comment
,
.diagnostics = &diagnostics,
}));
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source =
\\//! comment
\\10 // comment
\\// comment
,
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected expression, found 'a document comment'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon failure/oom formatting" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
try std.testing.expectError(error.OutOfMemory, fromSlice([]const u8, .{
.gpa = .failing,
.arena = arena,
.source = "\"foo\"",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\
, "{f}", .{diagnostics.fmt("input.zon")});
}
test "std.zon fromSlice syntax error" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: expected expression, found 'EOF'
, "{f}", .{diagnostics.fmt("input.zon")});
}
test "std.zon optional" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Basic usage
{
const none = try fromSliceNoAlloc(?u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expect(none == null);
const some = try fromSliceNoAlloc(?u32, .{
.gpa = gpa,
.arena = arena,
.source = "1",
.diagnostics = &diagnostics,
});
try std.testing.expect(some.? == 1);
}
// Deep free
{
const none = try fromSlice(?[]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expect(none == null);
const some = try fromSlice(?[]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"foo\"",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings("foo", some.?);
}
}
test "std.zon unions" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// 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 fromSliceNoAlloc(Tagged, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.5}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x);
const tagged_y = try fromSliceNoAlloc(Tagged, .{
.gpa = gpa,
.arena = arena,
.source = ".{.@\"y y\" = true}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y);
const tagged_z_shorthand = try fromSliceNoAlloc(Tagged, .{
.gpa = gpa,
.arena = arena,
.source = ".z",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand);
const tagged_zz_shorthand = try fromSliceNoAlloc(Tagged, .{
.gpa = gpa,
.arena = arena,
.source = ".@\"z z\"",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand);
const untagged_x = try fromSliceNoAlloc(Untagged, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.5}",
.diagnostics = &diagnostics,
});
try std.testing.expect(untagged_x.x == 1.5);
const untagged_y = try fromSliceNoAlloc(Untagged, .{
.gpa = gpa,
.arena = arena,
.source = ".{.@\"y y\" = true}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{.baz = false}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Union{ .baz = false }, noalloc);
const alloc = try fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".{.bar = \"qux\"}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc);
}
// Unknown field
{
const Union = union { x: f32, y: f32 };
try std.testing.expectError(
error.ParseZon,
fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".{.z=2.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:4: error: unexpected field 'z'
\\input.zon:1:4: note: supported: 'x', 'y'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Explicit void field
{
const Union = union(enum) { x: void };
try std.testing.expectError(
error.ParseZon,
fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x=1}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:6: error: expected type 'void'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Extra field
{
const Union = union { x: f32, y: bool };
try std.testing.expectError(
error.ParseZon,
fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.5, .y = true}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected union
, "{f}", .{diagnostics.fmt("input.zon")});
}
// No fields
{
const Union = union { x: f32, y: bool };
try std.testing.expectError(
error.ParseZon,
fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected union
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Enum literals cannot coerce into untagged unions
{
const Union = union { x: void };
try std.testing.expectError(error.ParseZon, fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".x",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:2: error: expected union
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Unknown field for enum literal coercion
{
const Union = union(enum) { x: void };
try std.testing.expectError(error.ParseZon, fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".y",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:2: error: unexpected field 'y'
\\input.zon:1:2: note: supported: 'x'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Non void field for enum literal coercion
{
const Union = union(enum) { x: f32 };
try std.testing.expectError(error.ParseZon, fromSlice(Union, .{
.gpa = gpa,
.arena = arena,
.source = ".x",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:2: error: expected union
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon structs" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// 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 fromSliceNoAlloc(Vec0, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec0{}, zero);
const one = try fromSliceNoAlloc(Vec1, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec1{ .x = 1.2 }, one);
const two = try fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2, .y = 3.4}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two);
const three = try fromSliceNoAlloc(Vec3, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2, .y = 3.4, .z = 5.6}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed);
}
// Unknown field
{
const Vec2 = struct { x: f32, y: f32 };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x=1.5, .z=2.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:12: error: unexpected field 'z'
\\input.zon:1:12: note: supported: 'x', 'y'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Duplicate field
{
const Vec2 = struct { x: f32, y: f32 };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x=1.5, .x=2.5, .x=3.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:4: error: duplicate struct field name
\\input.zon:1:12: note: duplicate name here
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Ignore unknown fields
{
const Vec2 = struct { x: f32, y: f32 = 2.0 };
const parsed = try fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .x = 1.0, .z = 3.0 }",
.ignore_unknown_fields = true,
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec2{ .x = 1.0, .y = 2.0 }, parsed);
}
// Unknown field when struct has no fields (regression test)
{
const Vec2 = struct {};
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x=1.5, .z=2.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:4: error: unexpected field 'x'
\\input.zon:1:4: note: none expected
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Missing field
{
const Vec2 = struct { x: f32, y: f32 };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x=1.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: missing required field y
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Default field
{
const Vec2 = struct { x: f32, y: f32 = 1.5 };
const parsed = try fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2}",
.diagnostics = &diagnostics,
});
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 fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2}",
.diagnostics = &diagnostics,
});
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 };
const parsed = fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{.x = 1.2, .y = 1.5}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:18: error: cannot initialize comptime field
, "{f}", .{diagnostics.fmt("input.zon")});
}
// 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 fromSliceNoAlloc(Vec0, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .x = .x }",
.diagnostics = &diagnostics,
});
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 fromSliceNoAlloc(Vec0, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .@\"x x\" = .@\"x x\" }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed);
}
// Type expressions are not allowed
{
// Structs
{
const parsed = fromSliceNoAlloc(struct {}, .{
.gpa = gpa,
.arena = arena,
.source = "Empty{}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:1: error: types are not available in ZON
\\input.zon:1:1: note: replace the type with '.'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Arrays
{
const parsed = fromSliceNoAlloc([3]u8, .{
.gpa = gpa,
.arena = arena,
.source = "[3]u8{1, 2, 3}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:1: error: types are not available in ZON
\\input.zon:1:1: note: replace the type with '.'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Slices
{
const parsed = fromSlice([]u8, .{
.gpa = gpa,
.arena = arena,
.source = "[]u8{1, 2, 3}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:1: error: types are not available in ZON
\\input.zon:1:1: note: replace the type with '.'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Tuples
{
const parsed = fromSliceNoAlloc(struct { u8, u8, u8 }, .{
.gpa = gpa,
.arena = arena,
.source = "Tuple{1, 2, 3}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:1: error: types are not available in ZON
\\input.zon:1:1: note: replace the type with '.'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Nested
{
const parsed = fromSliceNoAlloc(struct {}, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .x = Tuple{1, 2, 3} }",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:9: error: types are not available in ZON
\\input.zon:1:9: note: replace the type with '.'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
}
test "std.zon tuples" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// 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 fromSliceNoAlloc(Tuple0, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Tuple0{}, zero);
const one = try fromSliceNoAlloc(Tuple1, .{
.gpa = gpa,
.arena = arena,
.source = ".{1.2}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Tuple1{1.2}, one);
const two = try fromSliceNoAlloc(Tuple2, .{
.gpa = gpa,
.arena = arena,
.source = ".{1.2, true}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Tuple2{ 1.2, true }, two);
const three = try fromSliceNoAlloc(Tuple3, .{
.gpa = gpa,
.arena = arena,
.source = ".{1.2, false, 3}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{\"hello\", \"world\"}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed);
}
// Extra field
{
const Tuple = struct { f32, bool };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Tuple, .{
.gpa = gpa,
.arena = arena,
.source = ".{0.5, true, 123}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:14: error: index 2 outside of tuple length 2
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Extra field
{
const Tuple = struct { f32, bool };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Tuple, .{
.gpa = gpa,
.arena = arena,
.source = ".{0.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: missing tuple field with index 1
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Tuple with unexpected field names
{
const Tuple = struct { f32 };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Tuple, .{
.gpa = gpa,
.arena = arena,
.source = ".{.foo = 10.0}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected tuple
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Struct with missing field names
{
const Struct = struct { foo: f32 };
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Struct, .{
.gpa = gpa,
.arena = arena,
.source = ".{10.0}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected struct
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Comptime field
{
const Vec2 = struct { f32, comptime f32 = 1.5 };
const parsed = try fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{ 1.2 }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed);
}
// Comptime field assignment
{
const Vec2 = struct { f32, comptime f32 = 1.5 };
const parsed = fromSliceNoAlloc(Vec2, .{
.gpa = gpa,
.arena = arena,
.source = ".{ 1.2, 1.5}",
.diagnostics = &diagnostics,
});
try std.testing.expectError(error.ParseZon, parsed);
try std.testing.expectFmt(
\\input.zon:1:9: error: cannot initialize comptime field
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Test sizes 0 to 3 since small sizes get parsed differently
test "std.zon arrays and slices" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Literals
{
// Arrays
{
const zero = try fromSliceNoAlloc([0]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero);
const one = try fromSliceNoAlloc([1]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one);
const two = try fromSliceNoAlloc([2]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two);
const two_comma = try fromSliceNoAlloc([2]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b',}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma);
const three = try fromSliceNoAlloc([3]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three);
const sentinel = try fromSliceNoAlloc([3:'z']u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero);
const one = try fromSlice([]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &.{'a'}, one);
const two = try fromSlice([]const u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two);
const two_comma = try fromSlice([]const u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b',}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma);
const three = try fromSlice([]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three);
const sentinel = try fromSlice([:'z']const u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{\"abc\"}",
.diagnostics = &diagnostics,
});
const expected: [1][]const u8 = .{"abc"};
try std.testing.expectEqualDeep(expected, parsed);
}
// Slice literals
{
const parsed = try fromSlice([]const []const u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{\"abc\"}",
.diagnostics = &diagnostics,
});
const expected: []const []const u8 = &.{"abc"};
try std.testing.expectEqualDeep(expected, parsed);
}
}
// Sentinels and alignment
{
// Arrays
{
const sentinel = try fromSliceNoAlloc([1:2]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{1}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{1}",
.diagnostics = &diagnostics,
});
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
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([0]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: index 0 outside of array of length 0
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Expect 1 find 2
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([1]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b'}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:8: error: index 1 outside of array of length 1
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Expect 2 find 1
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([2]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a'}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected 2 array elements; found 1
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Expect 3 find 0
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([3]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected 3 array elements; found 0
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Wrong inner type
{
// Array
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([3]bool, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: expected type 'bool'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Slice
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]bool, .{
.gpa = gpa,
.arena = arena,
.source = ".{'a', 'b', 'c'}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: expected type 'bool'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Complete wrong type
{
// Array
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([3]u8, .{
.gpa = gpa,
.arena = arena,
.source = "'a'",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Slice
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Address of is not allowed (indirection for slices in ZON is implicit)
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = " &.{'a', 'b', 'c'}", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: pointers are not available in ZON
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon string literal" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Basic string literal
{
const parsed = try fromSlice([]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"abc\"",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed);
}
// String literal with escape characters
{
const parsed = try fromSlice([]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"ab\\nc\"",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed);
}
// String literal with embedded null
{
const parsed = try fromSlice([]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"ab\\x00c\"",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed);
}
// Passing string literal to a mutable slice
{
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\"abcd\"", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abcd", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Passing string literal to a array
{
{
var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .{ .mode = .zon });
defer ast.deinit(gpa);
var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([4:0]u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"abcd\"",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc([4:0]u8, .{
.gpa = gpa,
.arena = arena,
.source = "\\\\abcd",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Zero terminated slices
{
{
const parsed: [:0]const u8 = try fromSlice([:0]const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"abc\"",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = "\\\\abc",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings("abc", parsed);
try std.testing.expectEqual(@as(u8, 0), parsed[3]);
}
}
// Other value terminated slices
{
{
try std.testing.expectError(
error.ParseZon,
fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\foo", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Expecting string literal, getting something else
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected string
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Expecting string literal, getting an incompatible tuple
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{false}", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: expected type 'u8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Invalid string literal
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"\\a\"", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:3: error: invalid escape character: 'a'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Slice wrong child type
{
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"a\"", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\\\\a", .diagnostics = &diagnostics }),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// Bad alignment
{
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]align(2) const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\"abc\"",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice([]align(2) const u8, .{
.gpa = gpa,
.arena = arena,
.source = "\\\\abc",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected array
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
// 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 = gpa,
.arena = arena,
.source =
\\.{
\\ .message =
\\ \\hello, world!
\\
\\ \\this is a multiline string!
\\ \\
\\ \\...
\\
\\ ,
\\ .message2 =
\\ \\this too...sort of.
\\ ,
\\ .message3 =
\\ \\
\\ \\and this.
\\}
,
.diagnostics = &diagnostics,
});
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;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const Enum = enum {
foo,
bar,
baz,
@"ab\nc",
};
// Tags that exist
try std.testing.expectEqual(Enum.foo, try fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".foo",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(Enum.bar, try fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".bar",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(Enum.baz, try fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".baz",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(
Enum.@"ab\nc",
try fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".@\"ab\\nc\"",
.diagnostics = &diagnostics,
}),
);
// Bad tag
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".qux",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: unexpected enum literal 'qux'
\\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Bad tag that's too long for parser
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".@\"foobarbaz\"",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: unexpected enum literal 'foobarbaz'
\\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Bad type
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected enum literal
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Test embedded nulls in an identifier
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(Enum, .{
.gpa = gpa,
.arena = arena,
.source = ".@\"\\x00\"",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: identifier cannot contain null bytes
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon parse bool" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Correct bools
try std.testing.expectEqual(true, try fromSliceNoAlloc(bool, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(false, try fromSliceNoAlloc(bool, .{
.gpa = gpa,
.arena = arena,
.source = "false",
.diagnostics = &diagnostics,
}));
// Errors
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(bool, .{
.gpa = gpa,
.arena = arena,
.source = " foo",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: invalid expression
\\input.zon:1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'
\\input.zon:1:2: note: precede identifier with '.' for an enum literal
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(bool, .{
.gpa = gpa,
.arena = arena,
.source = "123",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'bool'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
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))).?);
try std.testing.expectEqual(@as(u128, 67), intFromFloatExact(u128, @as(f128, @as(f128, 67.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;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Test various numbers and types
try std.testing.expectEqual(@as(u8, 10), try fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, 24), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "24",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i14, -4), try fromSliceNoAlloc(i14, .{
.gpa = gpa,
.arena = arena,
.source = "-4",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i32, -123), try fromSliceNoAlloc(i32, .{
.gpa = gpa,
.arena = arena,
.source = "-123",
.diagnostics = &diagnostics,
}));
// Test limits
try std.testing.expectEqual(@as(i8, 127), try fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "127",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i8, -128), try fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "-128",
.diagnostics = &diagnostics,
}));
// Test characters
try std.testing.expectEqual(@as(u8, 'a'), try fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "'a'",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(u8, 'z'), try fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "'z'",
.diagnostics = &diagnostics,
}));
// Test big integers
try std.testing.expectEqual(
@as(u65, 36893488147419103231),
try fromSliceNoAlloc(u65, .{
.gpa = gpa,
.arena = arena,
.source = "36893488147419103231",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(u65, 36893488147419103231),
try fromSliceNoAlloc(u65, .{
.gpa = gpa,
.arena = arena,
.source = "368934_881_474191032_31",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(u128, 340282366920938463463374607431768211455),
try fromSliceNoAlloc(u128, .{
.gpa = gpa,
.arena = arena,
.source = "340282366920938463463374607431768211455",
.diagnostics = &diagnostics,
}),
);
// Test big integer limits
try std.testing.expectEqual(
@as(i66, 36893488147419103231),
try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "36893488147419103231",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(i66, -36893488147419103232),
try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "-36893488147419103232",
.diagnostics = &diagnostics,
}),
);
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "36893488147419103232",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'i66' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "-36893488147419103233",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'i66' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Test parsing whole number floats as integers
try std.testing.expectEqual(@as(i8, -1), try fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "-1.0",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i8, 123), try fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "123.0",
.diagnostics = &diagnostics,
}));
// Test non-decimal integers
try std.testing.expectEqual(@as(i16, 0xff), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "0xff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, -0xff), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "-0xff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, 0o77), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "0o77",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, -0o77), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "-0o77",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, 0b11), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "0b11",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i16, -0b11), try fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "-0b11",
.diagnostics = &diagnostics,
}));
// Test non-decimal big integers
try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{
.gpa = gpa,
.arena = arena,
.source = "0x1ffffffffffffffff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "0x1ffffffffffffffff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "-0x1ffffffffffffffff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{
.gpa = gpa,
.arena = arena,
.source = "0o3777777777777777777777",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "0o3777777777777777777777",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "-0o3777777777777777777777",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{
.gpa = gpa,
.arena = arena,
.source = "0b11111111111111111111111111111111111111111111111111111111111111111",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "0b11111111111111111111111111111111111111111111111111111111111111111",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{
.gpa = gpa,
.arena = arena,
.source = "-0b11111111111111111111111111111111111111111111111111111111111111111",
.diagnostics = &diagnostics,
}));
// Number with invalid character in the middle
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "32a32",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:3: error: invalid digit 'a' for decimal base
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing to parse as int
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'u8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing because an int is out of range
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "256",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'u8' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing because a negative int is out of range
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "-129",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'i8' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing because an unsigned int is negative
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "-1",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'u8' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing because a float is non-whole
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "1.5",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'u8' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Failing because a float is negative
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "-1.0",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: type 'u8' cannot represent value
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Negative integer zero
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "-0",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:2: error: integer literal '-0' is ambiguous
\\input.zon:1:2: note: use '0' for an integer zero
\\input.zon:1:2: note: use '-0.0' for a floating-point signed zero
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Negative integer zero casted to float
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-0",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:2: error: integer literal '-0' is ambiguous
\\input.zon:1:2: note: use '0' for an integer zero
\\input.zon:1:2: note: use '-0.0' for a floating-point signed zero
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Negative float 0 is allowed
try std.testing.expect(
std.math.isNegativeZero(try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-0.0",
.diagnostics = &diagnostics,
})),
);
try std.testing.expect(std.math.isPositiveZero(try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "0.0",
.diagnostics = &diagnostics,
})));
// Double negation is not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "--2",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "--2.0",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Invalid int literal
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "0xg",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:3: error: invalid digit 'g' for hex base
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Notes on invalid int literal
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{
.gpa = gpa,
.arena = arena,
.source = "0123",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: number '0123' has leading zero
\\input.zon:1:1: note: use '0o' prefix for octal literals
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon negative char" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-'a'",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i16, .{
.gpa = gpa,
.arena = arena,
.source = "-'a'",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon parse float" {
if (builtin.cpu.arch == .x86) return error.SkipZigTest;
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Test decimals
try std.testing.expectEqual(@as(f16, 0.5), try fromSliceNoAlloc(f16, .{
.gpa = gpa,
.arena = arena,
.source = "0.5",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(
@as(f32, 123.456),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "123.456",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(f64, -123.456),
try fromSliceNoAlloc(f64, .{
.gpa = gpa,
.arena = arena,
.source = "-123.456",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(@as(f128, 42.5), try fromSliceNoAlloc(f128, .{
.gpa = gpa,
.arena = arena,
.source = "42.5",
.diagnostics = &diagnostics,
}));
// Test whole numbers with and without decimals
try std.testing.expectEqual(@as(f16, 5.0), try fromSliceNoAlloc(f16, .{
.gpa = gpa,
.arena = arena,
.source = "5.0",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(f16, 5.0), try fromSliceNoAlloc(f16, .{
.gpa = gpa,
.arena = arena,
.source = "5",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(f32, -102), try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-102.0",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(f32, -102), try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-102",
.diagnostics = &diagnostics,
}));
// Test characters and negated characters
try std.testing.expectEqual(@as(f32, 'a'), try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "'a'",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(f32, 'z'), try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "'z'",
.diagnostics = &diagnostics,
}));
// Test big integers
try std.testing.expectEqual(
@as(f32, 36893488147419103231.0),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "36893488147419103231",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(f32, -36893488147419103231.0),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-36893488147419103231",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSliceNoAlloc(f128, .{
.gpa = gpa,
.arena = arena,
.source = "0x1ffffffffffffffff",
.diagnostics = &diagnostics,
}));
try std.testing.expectEqual(@as(f32, @floatFromInt(0x1ffffffffffffffff)), try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "0x1ffffffffffffffff",
.diagnostics = &diagnostics,
}));
// Exponents, underscores
try std.testing.expectEqual(
@as(f32, 123.0E+77),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "12_3.0E+77",
.diagnostics = &diagnostics,
}),
);
// Hexadecimal
try std.testing.expectEqual(
@as(f32, 0x103.70p-5),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "0x103.70p-5",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(f32, -0x103.70),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-0x103.70",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@as(f32, 0x1234_5678.9ABC_CDEFp-10),
try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "0x1234_5678.9ABC_CDEFp-10",
.diagnostics = &diagnostics,
}),
);
// inf, nan
try std.testing.expect(std.math.isPositiveInf(try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "inf",
.diagnostics = &diagnostics,
})));
try std.testing.expect(std.math.isNegativeInf(try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-inf",
.diagnostics = &diagnostics,
})));
try std.testing.expect(std.math.isNan(try fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "nan",
.diagnostics = &diagnostics,
})));
// Negative nan not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-nan",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// nan as int not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "nan",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'i8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// nan as int not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "nan",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'i8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// inf as int not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "inf",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'i8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// -inf as int not allowed
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{
.gpa = gpa,
.arena = arena,
.source = "-inf",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'i8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Bad identifier as float
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "foo",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: invalid expression
\\input.zon:1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'
\\input.zon:1:1: note: precede identifier with '.' for an enum literal
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "-foo",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:1: error: expected number or 'inf' after '-'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Non float as float
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(f32, .{
.gpa = gpa,
.arena = arena,
.source = "\"foo\"",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type 'f32'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon free on error" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Test freeing partially allocated structs
{
const Struct = struct {
x: []const u8,
y: []const u8,
z: bool,
};
try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{
.gpa = gpa,
.arena = arena,
.source =
\\.{
\\ .x = "hello",
\\ .y = "world",
\\ .z = "fail",
\\}
,
.diagnostics = &diagnostics,
}));
}
// Test freeing partially allocated tuples
{
const Struct = struct {
[]const u8,
[]const u8,
bool,
};
try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{
.gpa = gpa,
.arena = arena,
.source =
\\.{
\\ "hello",
\\ "world",
\\ "fail",
\\}
,
.diagnostics = &diagnostics,
}));
}
// Test freeing structs with missing fields
const Struct = struct {
x: []const u8,
y: bool,
};
try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{
.gpa = gpa,
.arena = arena,
.source =
\\.{
\\ .x = "hello",
\\}
,
.diagnostics = &diagnostics,
}));
// Test freeing partially allocated arrays
try std.testing.expectError(error.ParseZon, fromSlice([3][]const u8, .{
.gpa = gpa,
.arena = arena,
.source =
\\.{
\\ "hello",
\\ false,
\\ false,
\\}
,
.diagnostics = &diagnostics,
}));
// Test freeing partially allocated slices
try std.testing.expectError(error.ParseZon, fromSlice([][]const u8, .{
.gpa = gpa,
.arena = arena,
.source =
\\.{
\\ "hello",
\\ "world",
\\ false,
\\}
,
.diagnostics = &diagnostics,
}));
// 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 fromSliceNoAlloc(union { x: f32 }, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .x = 1.5 }",
.diagnostics = &diagnostics,
})).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 }, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .x = \"foo\" }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualStrings("foo", result.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_llvm and builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25957
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Passing cases
try std.testing.expectEqual(
@Vector(0, bool){},
try fromSliceNoAlloc(@Vector(0, bool), .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@Vector(3, bool){ true, false, true },
try fromSliceNoAlloc(@Vector(3, bool), .{
.gpa = gpa,
.arena = arena,
.source = ".{true, false, true}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@Vector(0, f32){},
try fromSliceNoAlloc(@Vector(0, f32), .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@Vector(3, f32){ 1.5, 2.5, 3.5 },
try fromSliceNoAlloc(@Vector(3, f32), .{
.gpa = gpa,
.arena = arena,
.source = ".{1.5, 2.5, 3.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@Vector(0, u8){},
try fromSliceNoAlloc(@Vector(0, u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
@Vector(3, u8){ 2, 4, 6 },
try fromSliceNoAlloc(@Vector(3, u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{2, 4, 6}",
.diagnostics = &diagnostics,
}),
);
{
try std.testing.expectEqual(
@Vector(0, *const u8){},
try fromSlice(@Vector(0, *const u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
const pointers = try fromSlice(@Vector(3, *const u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{2, 4, 6}",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
}),
);
const pointers = try fromSlice(@Vector(3, ?*const u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{2, null, 6}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers);
}
// Too few fields
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(@Vector(2, f32), .{
.gpa = gpa,
.arena = arena,
.source = ".{0.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:2: error: expected 2 array elements; found 1
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Too many fields
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(@Vector(2, f32), .{
.gpa = gpa,
.arena = arena,
.source = ".{0.5, 1.5, 2.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:13: error: index 2 outside of array of length 2
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Wrong type fields
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(@Vector(3, f32), .{
.gpa = gpa,
.arena = arena,
.source = ".{0.5, true, 2.5}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:8: error: expected type 'f32'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Wrong type
{
try std.testing.expectError(
error.ParseZon,
fromSliceNoAlloc(@Vector(3, u8), .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type '@Vector(3, u8)'
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Elements should get freed on error
{
try std.testing.expectError(
error.ParseZon,
fromSlice(@Vector(3, *u8), .{
.gpa = gpa,
.arena = arena,
.source = ".{1, true, 3}",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:6: error: expected type 'u8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon add pointers" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Primitive with varying levels of pointers
{
const result = try fromSlice(*u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), result.*);
}
{
const result = try fromSlice(**u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), result.*.*);
}
{
const result = try fromSlice(***u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), result.*.*.*);
}
// Primitive optional with varying levels of pointers
{
const some = try fromSlice(?*u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), some.?.*);
const none = try fromSlice(?*u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(null, none);
}
{
const some = try fromSlice(*?u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), some.*.?);
const none = try fromSlice(*?u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(null, none.*);
}
{
const some = try fromSlice(?**u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), some.?.*.*);
const none = try fromSlice(?**u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(null, none);
}
{
const some = try fromSlice(*?*u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), some.*.?.*);
const none = try fromSlice(*?*u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(null, none.*);
}
{
const some = try fromSlice(**?u32, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(@as(u32, 10), some.*.*.?);
const none = try fromSlice(**?u32, .{
.gpa = gpa,
.arena = arena,
.source = "null",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(null, none.*.*);
}
// Pointer to an array
{
const result = try fromSlice(*[3]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{ 1, 2, 3 }",
.diagnostics = &diagnostics,
});
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 = gpa,
.arena = arena,
.source =
\\.{
\\ .f1 = .{
\\ .f1 = null,
\\ .f2 = "foo",
\\ },
\\ .f2 = null,
\\}
,
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(expected, found.?.*);
}
// Test that optional types are flattened correctly in errors
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type '?u8'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const f32, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type '?f32'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const @Vector(3, u8), .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type '?@Vector(3, u8)'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const bool, .{
.gpa = gpa,
.arena = arena,
.source = "10",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected type '?bool'
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const struct { a: i32 }, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional struct
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const struct { i32 }, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional tuple
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const union { x: void }, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional union
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const [3]u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(?[3]u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const []u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(?[]u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional array
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const []const u8, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional string
, "{f}", .{diagnostics.fmt("input.zon")});
}
{
try std.testing.expectError(
error.ParseZon,
fromSlice(*const ?*const enum { foo }, .{
.gpa = gpa,
.arena = arena,
.source = "true",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectFmt(
\\input.zon:1:1: error: expected optional enum literal
, "{f}", .{diagnostics.fmt("input.zon")});
}
}
test "std.zon stop on node" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
{
const Vec2 = struct {
x: Zoir.Node.Index,
y: f32,
};
var ast = try std.zig.Ast.parse(gpa, ".{ .x = 1.5, .y = 2.5 }", .{ .mode = .zon });
defer ast.deinit(gpa);
var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
const result = try fromZoirNoAlloc(Vec2, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(result.y, 2.5);
try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.5 }, result.x.get(&zoir));
}
{
var ast = try std.zig.Ast.parse(gpa, "1.23", .{ .mode = .zon });
defer ast.deinit(gpa);
var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
const result = try fromZoirNoAlloc(Zoir.Node.Index, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.23 }, result.get(&zoir));
}
}
test "std.zon no alloc" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
try std.testing.expectEqual(
[3]u8{ 1, 2, 3 },
try fromSliceNoAlloc([3]u8, .{
.gpa = gpa,
.arena = arena,
.source = ".{ 1, 2, 3 }",
.diagnostics = &diagnostics,
}),
);
const Nested = struct { u8, u8, struct { u8, u8 } };
var ast = try std.zig.Ast.parse(gpa, ".{ 1, 2, .{ 3, 4 } }", .{ .mode = .zon });
defer ast.deinit(gpa);
var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
try std.testing.expectEqual(
Nested{ 1, 2, .{ 3, 4 } },
try fromZoirNoAlloc(Nested, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
}),
);
}
test "std.zon aligned pointers" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const n: u8 align(8) = 10;
const Foo = struct {
inner: *align(8) const u8,
};
const expected: Foo = .{
.inner = &n,
};
const found = try fromSlice(Foo, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .inner = 10 }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(expected.inner, found.inner);
}
test "std.zon update basic" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const Vector = struct { x: f32, y: f32, z: f32 };
const MyStruct = struct {
foo: u32 = 123,
bar: bool,
baz: struct {
a: [2]u32,
b: enum { c, d } = .d,
},
ptr: *Vector,
optional: ?u32 = null,
str: []const u8,
};
var v1: Vector = .{ .x = 1, .y = 2, .z = 3 };
var expected: MyStruct = .{
.foo = 10,
.bar = true,
.baz = .{ .a = .{ 1, 2 }, .b = .c },
.ptr = &v1,
.str = "hello, world",
};
var v2: Vector = .{ .x = 1, .y = 2, .z = 3 };
var found: MyStruct = .{
.foo = 10,
.bar = true,
.baz = .{ .a = .{ 1, 2 }, .b = .c },
.ptr = &v2,
.str = "hello, world",
};
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{}",
.diagnostics = &diagnostics,
});
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .bar = false }",
.diagnostics = &diagnostics,
});
expected.bar = false;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .baz = .{ .a = .{ 2, 4 } } }",
.diagnostics = &diagnostics,
});
expected.baz.a[0] = 2;
expected.baz.a[1] = 4;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = 11, .baz = .{ .b = .d } }",
.diagnostics = &diagnostics,
});
expected.foo = 11;
expected.baz.b = .d;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .optional = 10 }",
.diagnostics = &diagnostics,
});
expected.optional = 10;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .optional = null }",
.diagnostics = &diagnostics,
});
expected.optional = null;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .ptr = .{ .x = 10, .y = 20, .z = 30 } }",
.diagnostics = &diagnostics,
});
expected.ptr.x = 10;
expected.ptr.y = 20;
expected.ptr.z = 30;
try std.testing.expectEqualDeep(expected, found);
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .str = \"foo\" }",
.diagnostics = &diagnostics,
});
expected.str = "foo";
try std.testing.expectEqualDeep(expected, found);
}
test "std.zon update optionals" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const MyStruct = struct {
foo: ?struct { bar: u32 = 1, baz: u32 = 2, qux: u32 },
};
// Updating an optional that starts out null should fill in any default fields we left off
{
var expected: MyStruct = .{ .foo = null };
var found = expected;
try updateFromSliceNoAlloc(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .qux = 3 } }",
.diagnostics = &diagnostics,
});
expected.foo = .{ .qux = 3 };
try std.testing.expectEqual(expected, found);
}
// Updating an optional that starts out null should error if we leave off required fields.
{
var found: MyStruct = .{ .foo = null };
try std.testing.expectError(error.ParseZon, updateFromSliceNoAlloc(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{} }",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:12: error: missing required field qux
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Updating an optional that starts out non-null should preserve any values we leave off. It's
// also okay to leave off required fields since they were already set.
{
var expected: MyStruct = .{
.foo = .{ .bar = 10, .baz = 20, .qux = 30 },
};
var found = expected;
try updateFromSliceNoAlloc(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .baz = 200 } }",
.diagnostics = &diagnostics,
});
expected.foo.?.baz = 200;
try std.testing.expectEqual(expected, found);
}
}
test "std.zon update optional pointers" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const MyStruct = struct {
foo: ?*const struct { bar: u32 = 1, baz: u32 = 2, qux: u32 },
};
// Just a smoke test since its the combination of behavior we've already tested separately
{
var expected: MyStruct = .{ .foo = null };
var found = expected;
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .qux = 3 } }",
.diagnostics = &diagnostics,
});
expected.foo = &.{ .qux = 3 };
try std.testing.expectEqualDeep(expected, found);
}
}
test "std.zon update pointers" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
// Updating a pointer should leave fields we don't specify unchanged
{
const MyStruct = struct {
foo: *struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
};
var ptr: @typeInfo(@FieldType(MyStruct, "foo")).pointer.child = .{ .qux = 3 };
var expected: MyStruct = .{ .foo = &ptr };
var found = expected;
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .bar = 100 } }",
.diagnostics = &diagnostics,
});
expected.foo.bar = 100;
try std.testing.expectEqualDeep(expected, found);
}
// Same thing, but for const pointers
{
const MyStruct = struct {
foo: *const struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
};
var expected: MyStruct = .{ .foo = &.{ .qux = 3 } };
var found = expected;
try updateFromSlice(MyStruct, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .bar = 100 } }",
.diagnostics = &diagnostics,
});
expected.foo = &.{ .bar = 100, .baz = 20, .qux = 3 };
try std.testing.expectEqualDeep(expected, found);
}
}
test "std.zon update unions" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const MyUnion = union(enum) {
none: void,
foo: struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
};
// Updating a union to a new field should fill in any default sub-fields we left off
{
var expected: MyUnion = .none;
var found = expected;
try updateFromSliceNoAlloc(MyUnion, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .qux = 3 } }",
.diagnostics = &diagnostics,
});
expected = .{ .foo = .{ .qux = 3 } };
try std.testing.expectEqual(expected, found);
}
// Updating a union to a new field should error if we leave off required sub-fields
{
var found: MyUnion = .none;
try std.testing.expectError(error.ParseZon, updateFromSliceNoAlloc(MyUnion, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{} }",
.diagnostics = &diagnostics,
}));
try std.testing.expectFmt(
\\input.zon:1:12: error: missing required field qux
, "{f}", .{diagnostics.fmt("input.zon")});
}
// Updating a union should preseve any sub-fields that we left off. It's also okay to leave off
// required fields since they were already set.
{
var expected: MyUnion = .{
.foo = .{ .bar = 10, .baz = 20, .qux = 30 },
};
var found = expected;
try updateFromSliceNoAlloc(MyUnion, &found, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .foo = .{ .baz = 200 } }",
.diagnostics = &diagnostics,
});
expected.foo.baz = 200;
try std.testing.expectEqual(expected, found);
}
}
test "std.zon variants" {
// Just a smoke test, we don't need to test each variant thoroughly because they all call into
// the same code.
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
const Struct = struct { a: u32, b: u32 };
const start: Struct = .{ .a = 10, .b = 20 };
const end: Struct = .{ .a = 100, .b = 20 };
// Update
{
var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100 }", .{ .mode = .zon });
defer ast.deinit(gpa);
const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
var curr = start;
try updateFromSliceNoAlloc(Struct, &curr, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .a = 100 }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(end, curr);
curr = start;
try updateFromSlice(Struct, &curr, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .a = 100 }",
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(end, curr);
curr = start;
try updateFromZoirNoAlloc(Struct, &curr, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(end, curr);
curr = start;
try updateFromZoir(Struct, &curr, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
});
try std.testing.expectEqual(end, curr);
}
// From
{
var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100, .b = 20 }", .{ .mode = .zon });
defer ast.deinit(gpa);
const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
try std.testing.expectEqual(
end,
try fromSliceNoAlloc(Struct, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .a = 100, .b = 20 }",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
end,
try fromSlice(Struct, .{
.gpa = gpa,
.arena = arena,
.source = ".{ .a = 100, .b = 20 }",
.diagnostics = &diagnostics,
}),
);
try std.testing.expectEqual(
try fromZoirNoAlloc(Struct, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
}),
end,
);
try std.testing.expectEqual(
try fromZoir(Struct, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.diagnostics = &diagnostics,
}),
end,
);
}
}
test "std.zon parse specific node with zoir" {
const gpa = std.testing.allocator;
var arena_allocator: ArenaAllocator = .init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var diagnostics: Diagnostics = undefined;
var ast = try std.zig.Ast.parse(gpa, ".{ .x = 1.5, .y = 2.5 }", .{ .mode = .zon });
defer ast.deinit(gpa);
var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
defer zoir.deinit(gpa);
const root_node: std.zig.Zoir.Node = std.zig.Zoir.Node.Index.get(.root, &zoir);
try std.testing.expect(root_node == .struct_literal);
try std.testing.expectEqual(2, root_node.struct_literal.vals.len);
const x_node = root_node.struct_literal.vals.at(0);
const x_result = try fromZoirNoAlloc(f32, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.node = x_node,
.diagnostics = &diagnostics,
});
try std.testing.expectApproxEqAbs(x_result, 1.5, 0.1);
const y_node = root_node.struct_literal.vals.at(1);
const y_result = try fromZoirNoAlloc(f32, .{
.arena = arena,
.ast = &ast,
.zoir = &zoir,
.node = y_node,
.diagnostics = &diagnostics,
});
try std.testing.expectApproxEqAbs(y_result, 2.5, 0.1);
}
test {
_ = Diagnostics;
}