aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/Build/Fuzz.zig4
-rw-r--r--lib/std/Build/Module.zig34
-rw-r--r--lib/std/Build/Step/Compile.zig45
-rw-r--r--lib/std/Random.zig3
-rw-r--r--lib/std/Target.zig14
-rw-r--r--lib/std/Thread/Futex.zig2
-rw-r--r--lib/std/c.zig235
-rw-r--r--lib/std/compress.zig10
-rw-r--r--lib/std/debug.zig1
-rw-r--r--lib/std/debug/MemoryAccessor.zig11
-rw-r--r--lib/std/fs/File.zig2
-rw-r--r--lib/std/heap.zig41
-rw-r--r--lib/std/heap/WasmAllocator.zig7
-rw-r--r--lib/std/heap/WasmPageAllocator.zig233
-rw-r--r--lib/std/heap/general_purpose_allocator.zig15
-rw-r--r--lib/std/math/big/int.zig42
-rw-r--r--lib/std/math/big/int_test.zig438
-rw-r--r--lib/std/math/big/rational.zig96
-rw-r--r--lib/std/mem/Allocator.zig5
-rw-r--r--lib/std/meta.zig7
-rw-r--r--lib/std/os/linux.zig52
-rw-r--r--lib/std/os/linux/test.zig2
-rw-r--r--lib/std/os/windows.zig2
-rw-r--r--lib/std/posix.zig96
-rw-r--r--lib/std/priority_queue.zig18
-rw-r--r--lib/std/process/Child.zig56
-rw-r--r--lib/std/std.zig1
-rw-r--r--lib/std/testing.zig51
-rw-r--r--lib/std/time.zig6
-rw-r--r--lib/std/zig.zig7
-rw-r--r--lib/std/zig/Ast.zig17
-rw-r--r--lib/std/zig/AstGen.zig113
-rw-r--r--lib/std/zig/Zir.zig30
-rw-r--r--lib/std/zig/Zoir.zig29
-rw-r--r--lib/std/zig/ZonGen.zig208
-rw-r--r--lib/std/zig/string_literal.zig102
-rw-r--r--lib/std/zig/system/darwin.zig7
-rw-r--r--lib/std/zig/target.zig10
-rw-r--r--lib/std/zon.zig45
-rw-r--r--lib/std/zon/parse.zig3449
-rw-r--r--lib/std/zon/stringify.zig2306
41 files changed, 7021 insertions, 831 deletions
diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig
index 6e837ebec2..4ebdfdf8e5 100644
--- a/lib/std/Build/Fuzz.zig
+++ b/lib/std/Build/Fuzz.zig
@@ -127,7 +127,7 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, ttyconf: std.io.tty.Config, par
if (show_error_msgs or show_compile_errors or show_stderr) {
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
- build_runner.printErrorMessages(gpa, &compile.step, ttyconf, stderr, false) catch {};
+ build_runner.printErrorMessages(gpa, &compile.step, .{ .ttyconf = ttyconf }, stderr, false) catch {};
}
const rebuilt_bin_path = result catch |err| switch (err) {
@@ -155,7 +155,7 @@ fn fuzzWorkerRun(
const stderr = std.io.getStdErr();
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
- build_runner.printErrorMessages(gpa, &run.step, ttyconf, stderr, false) catch {};
+ build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = ttyconf }, stderr, false) catch {};
return;
},
else => {
diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig
index 1f2b4f3fcb..f299946731 100644
--- a/lib/std/Build/Module.zig
+++ b/lib/std/Build/Module.zig
@@ -78,22 +78,51 @@ pub const SystemLib = struct {
pub const SearchStrategy = enum { paths_first, mode_first, no_fallback };
};
+pub const CSourceLanguage = enum {
+ c,
+ cpp,
+
+ objective_c,
+ objective_cpp,
+
+ /// Standard assembly
+ assembly,
+ /// Assembly with the C preprocessor
+ assembly_with_preprocessor,
+
+ pub fn internalIdentifier(self: CSourceLanguage) []const u8 {
+ return switch (self) {
+ .c => "c",
+ .cpp => "c++",
+ .objective_c => "objective-c",
+ .objective_cpp => "objective-c++",
+ .assembly => "assembler",
+ .assembly_with_preprocessor => "assembler-with-cpp",
+ };
+ }
+};
+
pub const CSourceFiles = struct {
root: LazyPath,
/// `files` is relative to `root`, which is
/// the build root by default
files: []const []const u8,
flags: []const []const u8,
+ /// By default, determines language of each file individually based on its file extension
+ language: ?CSourceLanguage,
};
pub const CSourceFile = struct {
file: LazyPath,
flags: []const []const u8 = &.{},
+ /// By default, determines language of each file individually based on its file extension
+ language: ?CSourceLanguage = null,
pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile {
return .{
.file = file.file.dupe(b),
.flags = b.dupeStrings(file.flags),
+ .language = file.language,
};
}
};
@@ -378,9 +407,11 @@ pub const AddCSourceFilesOptions = struct {
root: ?LazyPath = null,
files: []const []const u8,
flags: []const []const u8 = &.{},
+ /// By default, determines language of each file individually based on its file extension
+ language: ?CSourceLanguage = null,
};
-/// Handy when you have many C/C++ source files and want them all to have the same flags.
+/// Handy when you have many non-Zig source files and want them all to have the same flags.
pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
const b = m.owner;
const allocator = b.allocator;
@@ -399,6 +430,7 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void {
.root = options.root orelse b.path(""),
.files = b.dupeStrings(options.files),
.flags = b.dupeStrings(options.flags),
+ .language = options.language,
};
m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM");
}
diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig
index 32e5791fe4..25061917fa 100644
--- a/lib/std/Build/Step/Compile.zig
+++ b/lib/std/Build/Step/Compile.zig
@@ -1259,40 +1259,44 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
.c_source_file => |c_source_file| l: {
if (!my_responsibility) break :l;
- if (c_source_file.flags.len == 0) {
- if (prev_has_cflags) {
- try zig_args.append("-cflags");
- try zig_args.append("--");
- prev_has_cflags = false;
- }
- } else {
+ if (prev_has_cflags or c_source_file.flags.len != 0) {
try zig_args.append("-cflags");
for (c_source_file.flags) |arg| {
try zig_args.append(arg);
}
try zig_args.append("--");
- prev_has_cflags = true;
}
+ prev_has_cflags = (c_source_file.flags.len != 0);
+
+ if (c_source_file.language) |lang| {
+ try zig_args.append("-x");
+ try zig_args.append(lang.internalIdentifier());
+ }
+
try zig_args.append(c_source_file.file.getPath2(mod.owner, step));
+
+ if (c_source_file.language != null) {
+ try zig_args.append("-x");
+ try zig_args.append("none");
+ }
total_linker_objects += 1;
},
.c_source_files => |c_source_files| l: {
if (!my_responsibility) break :l;
- if (c_source_files.flags.len == 0) {
- if (prev_has_cflags) {
- try zig_args.append("-cflags");
- try zig_args.append("--");
- prev_has_cflags = false;
- }
- } else {
+ if (prev_has_cflags or c_source_files.flags.len != 0) {
try zig_args.append("-cflags");
- for (c_source_files.flags) |flag| {
- try zig_args.append(flag);
+ for (c_source_files.flags) |arg| {
+ try zig_args.append(arg);
}
try zig_args.append("--");
- prev_has_cflags = true;
+ }
+ prev_has_cflags = (c_source_files.flags.len != 0);
+
+ if (c_source_files.language) |lang| {
+ try zig_args.append("-x");
+ try zig_args.append(lang.internalIdentifier());
}
const root_path = c_source_files.root.getPath2(mod.owner, step);
@@ -1300,6 +1304,11 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
try zig_args.append(b.pathJoin(&.{ root_path, file }));
}
+ if (c_source_files.language != null) {
+ try zig_args.append("-x");
+ try zig_args.append("none");
+ }
+
total_linker_objects += c_source_files.files.len;
},
diff --git a/lib/std/Random.zig b/lib/std/Random.zig
index 8c68bdf6da..ae88d8b4fe 100644
--- a/lib/std/Random.zig
+++ b/lib/std/Random.zig
@@ -29,6 +29,9 @@ pub const RomuTrio = @import("Random/RomuTrio.zig");
pub const SplitMix64 = @import("Random/SplitMix64.zig");
pub const ziggurat = @import("Random/ziggurat.zig");
+/// Any comparison of this field may result in illegal behavior, since it may be set to
+/// `undefined` in cases where the random implementation does not have any associated
+/// state.
ptr: *anyopaque,
fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
diff --git a/lib/std/Target.zig b/lib/std/Target.zig
index 9eea755189..9e5cd44513 100644
--- a/lib/std/Target.zig
+++ b/lib/std/Target.zig
@@ -528,13 +528,13 @@ pub const Os = struct {
.freebsd => .{
.semver = .{
.min = .{ .major = 12, .minor = 0, .patch = 0 },
- .max = .{ .major = 14, .minor = 1, .patch = 0 },
+ .max = .{ .major = 14, .minor = 2, .patch = 0 },
},
},
.netbsd => .{
.semver = .{
.min = .{ .major = 8, .minor = 0, .patch = 0 },
- .max = .{ .major = 10, .minor = 0, .patch = 0 },
+ .max = .{ .major = 10, .minor = 1, .patch = 0 },
},
},
.openbsd => .{
@@ -553,31 +553,31 @@ pub const Os = struct {
.macos => .{
.semver = .{
.min = .{ .major = 13, .minor = 0, .patch = 0 },
- .max = .{ .major = 15, .minor = 2, .patch = 0 },
+ .max = .{ .major = 15, .minor = 3, .patch = 0 },
},
},
.ios => .{
.semver = .{
.min = .{ .major = 12, .minor = 0, .patch = 0 },
- .max = .{ .major = 18, .minor = 1, .patch = 0 },
+ .max = .{ .major = 18, .minor = 3, .patch = 0 },
},
},
.tvos => .{
.semver = .{
.min = .{ .major = 13, .minor = 0, .patch = 0 },
- .max = .{ .major = 18, .minor = 1, .patch = 0 },
+ .max = .{ .major = 18, .minor = 3, .patch = 0 },
},
},
.visionos => .{
.semver = .{
.min = .{ .major = 1, .minor = 0, .patch = 0 },
- .max = .{ .major = 2, .minor = 1, .patch = 0 },
+ .max = .{ .major = 2, .minor = 3, .patch = 0 },
},
},
.watchos => .{
.semver = .{
.min = .{ .major = 6, .minor = 0, .patch = 0 },
- .max = .{ .major = 11, .minor = 1, .patch = 0 },
+ .max = .{ .major = 11, .minor = 3, .patch = 0 },
},
},
diff --git a/lib/std/Thread/Futex.zig b/lib/std/Thread/Futex.zig
index fc02b8407c..c18caec7a6 100644
--- a/lib/std/Thread/Futex.zig
+++ b/lib/std/Thread/Futex.zig
@@ -543,7 +543,7 @@ const PosixImpl = struct {
// This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere.
var ts: c.timespec = undefined;
if (timeout) |timeout_ns| {
- std.posix.clock_gettime(c.CLOCK.REALTIME, &ts) catch unreachable;
+ ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch unreachable;
ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s));
ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
diff --git a/lib/std/c.zig b/lib/std/c.zig
index e00196f453..fbd0c1d55c 100644
--- a/lib/std/c.zig
+++ b/lib/std/c.zig
@@ -136,7 +136,7 @@ pub const dev_t = switch (native_os) {
pub const mode_t = switch (native_os) {
.linux => linux.mode_t,
.emscripten => emscripten.mode_t,
- .openbsd, .haiku, .netbsd, .solaris, .illumos, .wasi => u32,
+ .openbsd, .haiku, .netbsd, .solaris, .illumos, .wasi, .windows => u32,
.freebsd, .macos, .ios, .tvos, .watchos, .visionos, .dragonfly => u16,
else => u0,
};
@@ -213,6 +213,24 @@ pub const ARCH = switch (native_os) {
.linux => linux.ARCH,
else => void,
};
+
+// For use with posix.timerfd_create()
+// Actually, the parameter for the timerfd_create() function is an integer,
+// which means that the developer has to figure out which value is appropriate.
+// To make this easier and, above all, safer, because an incorrect value leads
+// to a panic, an enum is introduced which only allows the values
+// that actually work.
+pub const TIMERFD_CLOCK = timerfd_clockid_t;
+pub const timerfd_clockid_t = switch (native_os) {
+ .freebsd => enum(u32) {
+ REALTIME = 0,
+ MONOTONIC = 4,
+ _,
+ },
+ .linux => linux.timerfd_clockid_t,
+ else => clockid_t,
+};
+
pub const CLOCK = clockid_t;
pub const clockid_t = switch (native_os) {
.linux, .emscripten => linux.clockid_t,
@@ -3528,7 +3546,21 @@ pub const itimerspec = switch (native_os) {
};
pub const msghdr = switch (native_os) {
.linux => linux.msghdr,
- .openbsd, .emscripten, .dragonfly, .freebsd, .netbsd, .haiku, .solaris, .illumos => extern struct {
+ .openbsd,
+ .emscripten,
+ .dragonfly,
+ .freebsd,
+ .netbsd,
+ .haiku,
+ .solaris,
+ .illumos,
+ .macos,
+ .driverkit,
+ .ios,
+ .tvos,
+ .visionos,
+ .watchos,
+ => extern struct {
/// optional address
name: ?*sockaddr,
/// size of address
@@ -3548,7 +3580,21 @@ pub const msghdr = switch (native_os) {
};
pub const msghdr_const = switch (native_os) {
.linux => linux.msghdr_const,
- .openbsd, .emscripten, .dragonfly, .freebsd, .netbsd, .haiku, .solaris, .illumos => extern struct {
+ .openbsd,
+ .emscripten,
+ .dragonfly,
+ .freebsd,
+ .netbsd,
+ .haiku,
+ .solaris,
+ .illumos,
+ .macos,
+ .driverkit,
+ .ios,
+ .tvos,
+ .visionos,
+ .watchos,
+ => extern struct {
/// optional address
name: ?*const sockaddr,
/// size of address
@@ -3962,7 +4008,16 @@ pub const sigval = switch (native_os) {
else => void,
};
-pub const addrinfo = switch (native_os) {
+pub const addrinfo = if (builtin.abi.isAndroid()) extern struct {
+ flags: AI,
+ family: i32,
+ socktype: i32,
+ protocol: i32,
+ addrlen: socklen_t,
+ canonname: ?[*:0]u8,
+ addr: ?*sockaddr,
+ next: ?*addrinfo,
+} else switch (native_os) {
.linux, .emscripten => linux.addrinfo,
.windows => ws2_32.addrinfo,
.freebsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
@@ -4346,7 +4401,52 @@ pub const sa_family_t = switch (native_os) {
.solaris, .illumos => u16,
else => void,
};
-pub const AF = switch (native_os) {
+pub const AF = if (builtin.abi.isAndroid()) struct {
+ pub const UNSPEC = 0;
+ pub const UNIX = 1;
+ pub const LOCAL = 1;
+ pub const INET = 2;
+ pub const AX25 = 3;
+ pub const IPX = 4;
+ pub const APPLETALK = 5;
+ pub const NETROM = 6;
+ pub const BRIDGE = 7;
+ pub const ATMPVC = 8;
+ pub const X25 = 9;
+ pub const INET6 = 10;
+ pub const ROSE = 11;
+ pub const DECnet = 12;
+ pub const NETBEUI = 13;
+ pub const SECURITY = 14;
+ pub const KEY = 15;
+ pub const NETLINK = 16;
+ pub const ROUTE = NETLINK;
+ pub const PACKET = 17;
+ pub const ASH = 18;
+ pub const ECONET = 19;
+ pub const ATMSVC = 20;
+ pub const RDS = 21;
+ pub const SNA = 22;
+ pub const IRDA = 23;
+ pub const PPPOX = 24;
+ pub const WANPIPE = 25;
+ pub const LLC = 26;
+ pub const CAN = 29;
+ pub const TIPC = 30;
+ pub const BLUETOOTH = 31;
+ pub const IUCV = 32;
+ pub const RXRPC = 33;
+ pub const ISDN = 34;
+ pub const PHONET = 35;
+ pub const IEEE802154 = 36;
+ pub const CAIF = 37;
+ pub const ALG = 38;
+ pub const NFC = 39;
+ pub const VSOCK = 40;
+ pub const KCM = 41;
+ pub const QIPCRTR = 42;
+ pub const MAX = 43;
+} else switch (native_os) {
.linux, .emscripten => linux.AF,
.windows => ws2_32.AF,
.macos, .ios, .tvos, .watchos, .visionos => struct {
@@ -4579,7 +4679,52 @@ pub const AF = switch (native_os) {
},
else => void,
};
-pub const PF = switch (native_os) {
+pub const PF = if (builtin.abi.isAndroid()) struct {
+ pub const UNSPEC = AF.UNSPEC;
+ pub const UNIX = AF.UNIX;
+ pub const LOCAL = AF.LOCAL;
+ pub const INET = AF.INET;
+ pub const AX25 = AF.AX25;
+ pub const IPX = AF.IPX;
+ pub const APPLETALK = AF.APPLETALK;
+ pub const NETROM = AF.NETROM;
+ pub const BRIDGE = AF.BRIDGE;
+ pub const ATMPVC = AF.ATMPVC;
+ pub const X25 = AF.X25;
+ pub const PF_INET6 = AF.INET6;
+ pub const PF_ROSE = AF.ROSE;
+ pub const PF_DECnet = AF.DECnet;
+ pub const PF_NETBEUI = AF.NETBEUI;
+ pub const PF_SECURITY = AF.SECURITY;
+ pub const PF_KEY = AF.KEY;
+ pub const PF_NETLINK = AF.NETLINK;
+ pub const PF_ROUTE = AF.ROUTE;
+ pub const PF_PACKET = AF.PACKET;
+ pub const PF_ASH = AF.ASH;
+ pub const PF_ECONET = AF.ECONET;
+ pub const PF_ATMSVC = AF.ATMSVC;
+ pub const PF_RDS = AF.RDS;
+ pub const PF_SNA = AF.SNA;
+ pub const PF_IRDA = AF.IRDA;
+ pub const PF_PPPOX = AF.PPPOX;
+ pub const PF_WANPIPE = AF.WANPIPE;
+ pub const PF_LLC = AF.LLC;
+ pub const PF_CAN = AF.CAN;
+ pub const PF_TIPC = AF.TIPC;
+ pub const PF_BLUETOOTH = AF.BLUETOOTH;
+ pub const PF_IUCV = AF.IUCV;
+ pub const PF_RXRPC = AF.RXRPC;
+ pub const PF_ISDN = AF.ISDN;
+ pub const PF_PHONET = AF.PHONET;
+ pub const PF_IEEE802154 = AF.IEEE802154;
+ pub const PF_CAIF = AF.CAIF;
+ pub const PF_ALG = AF.ALG;
+ pub const PF_NFC = AF.NFC;
+ pub const PF_VSOCK = AF.VSOCK;
+ pub const PF_KCM = AF.KCM;
+ pub const PF_QIPCRTR = AF.QIPCRTR;
+ pub const PF_MAX = AF.MAX;
+} else switch (native_os) {
.linux, .emscripten => linux.PF,
.macos, .ios, .tvos, .watchos, .visionos => struct {
pub const UNSPEC = AF.UNSPEC;
@@ -6213,7 +6358,18 @@ pub const dirent64 = switch (native_os) {
else => void,
};
-pub const AI = switch (native_os) {
+pub const AI = if (builtin.abi.isAndroid()) packed struct(u32) {
+ PASSIVE: bool = false,
+ CANONNAME: bool = false,
+ NUMERICHOST: bool = false,
+ NUMERICSERV: bool = false,
+ _4: u4 = 0,
+ ALL: bool = false,
+ V4MAPPED_CFG: bool = false,
+ ADDRCONFIG: bool = false,
+ V4MAPPED: bool = false,
+ _: u20 = 0,
+} else switch (native_os) {
.linux, .emscripten => linux.AI,
.dragonfly, .haiku, .freebsd => packed struct(u32) {
PASSIVE: bool = false,
@@ -6260,7 +6416,11 @@ pub const AI = switch (native_os) {
PASSIVE: bool = false,
CANONNAME: bool = false,
NUMERICHOST: bool = false,
- _3: u9 = 0,
+ _3: u5 = 0,
+ ALL: bool = false,
+ V4MAPPED_CFG: bool = false,
+ ADDRCONFIG: bool = false,
+ V4MAPPED: bool = false,
NUMERICSERV: bool = false,
_: u19 = 0,
},
@@ -6292,7 +6452,40 @@ pub const NI = switch (native_os) {
else => void,
};
-pub const EAI = switch (native_os) {
+pub const EAI = if (builtin.abi.isAndroid()) enum(c_int) {
+ /// address family for hostname not supported
+ ADDRFAMILY = 1,
+ /// temporary failure in name resolution
+ AGAIN = 2,
+ /// invalid value for ai_flags
+ BADFLAGS = 3,
+ /// non-recoverable failure in name resolution
+ FAIL = 4,
+ /// ai_family not supported
+ FAMILY = 5,
+ /// memory allocation failure
+ MEMORY = 6,
+ /// no address associated with hostname
+ NODATA = 7,
+ /// hostname nor servname provided, or not known
+ NONAME = 8,
+ /// servname not supported for ai_socktype
+ SERVICE = 9,
+ /// ai_socktype not supported
+ SOCKTYPE = 10,
+ /// system error returned in errno
+ SYSTEM = 11,
+ /// invalid value for hints
+ BADHINTS = 12,
+ /// resolved protocol is unknown
+ PROTOCOL = 13,
+ /// argument buffer overflow
+ OVERFLOW = 14,
+
+ MAX = 15,
+
+ _,
+} else switch (native_os) {
.linux, .emscripten => enum(c_int) {
BADFLAGS = -1,
NONAME = -2,
@@ -9059,7 +9252,11 @@ pub const getentropy = switch (native_os) {
};
pub const getrandom = switch (native_os) {
.freebsd => private.getrandom,
- .linux => if (versionCheck(.{ .major = 2, .minor = 25, .patch = 0 })) private.getrandom else {},
+ .linux => if (builtin.abi.isMusl() or
+ (builtin.abi.isGnu() and versionCheck(.{ .major = 2, .minor = 25, .patch = 0 })) or
+ (builtin.abi.isAndroid() and versionCheck(.{ .major = 28, .minor = 0, .patch = 0 })))
+ private.getrandom
+ else {},
else => {},
};
@@ -9077,7 +9274,7 @@ pub extern "c" fn epoll_pwait(
sigmask: *const sigset_t,
) c_int;
-pub extern "c" fn timerfd_create(clockid: clockid_t, flags: c_int) c_int;
+pub extern "c" fn timerfd_create(clockid: timerfd_clockid_t, flags: c_int) c_int;
pub extern "c" fn timerfd_settime(
fd: c_int,
flags: c_int,
@@ -9302,8 +9499,8 @@ pub const fork = switch (native_os) {
pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;
pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;
pub extern "c" fn pipe(fds: *[2]fd_t) c_int;
-pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
-pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
+pub extern "c" fn mkdir(path: [*:0]const u8, mode: mode_t) c_int;
+pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: mode_t) c_int;
pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
pub extern "c" fn symlinkat(oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int;
pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
@@ -9388,11 +9585,11 @@ pub extern "c" fn malloc(usize) ?*anyopaque;
pub extern "c" fn realloc(?*anyopaque, usize) ?*anyopaque;
pub extern "c" fn free(?*anyopaque) void;
-pub extern "c" fn futimes(fd: fd_t, times: *[2]timeval) c_int;
-pub extern "c" fn utimes(path: [*:0]const u8, times: *[2]timeval) c_int;
+pub extern "c" fn futimes(fd: fd_t, times: ?*[2]timeval) c_int;
+pub extern "c" fn utimes(path: [*:0]const u8, times: ?*[2]timeval) c_int;
-pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: *[2]timespec, flags: u32) c_int;
-pub extern "c" fn futimens(fd: fd_t, times: *const [2]timespec) c_int;
+pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*:0]const u8, times: ?*[2]timespec, flags: u32) c_int;
+pub extern "c" fn futimens(fd: fd_t, times: ?*const [2]timespec) c_int;
pub extern "c" fn pthread_create(
noalias newthread: *pthread_t,
@@ -9789,16 +9986,19 @@ pub const _dyld_get_image_vmaddr_slide = darwin._dyld_get_image_vmaddr_slide;
pub const _dyld_image_count = darwin._dyld_image_count;
pub const _host_page_size = darwin._host_page_size;
pub const clock_get_time = darwin.clock_get_time;
+pub const @"close$NOCANCEL" = darwin.@"close$NOCANCEL";
pub const dispatch_release = darwin.dispatch_release;
pub const dispatch_semaphore_create = darwin.dispatch_semaphore_create;
pub const dispatch_semaphore_signal = darwin.dispatch_semaphore_signal;
pub const dispatch_semaphore_wait = darwin.dispatch_semaphore_wait;
pub const dispatch_time = darwin.dispatch_time;
pub const fcopyfile = darwin.fcopyfile;
+pub const host_t = darwin.host_t;
pub const ipc_space_t = darwin.ipc_space_t;
pub const ipc_space_port_t = darwin.ipc_space_port_t;
pub const kern_return_t = darwin.kern_return_t;
pub const kevent64 = darwin.kevent64;
+pub const kevent64_s = darwin.kevent64_s;
pub const mach_absolute_time = darwin.mach_absolute_time;
pub const mach_continuous_time = darwin.mach_continuous_time;
pub const mach_hdr = darwin.mach_hdr;
@@ -9822,6 +10022,7 @@ pub const mach_vm_region = darwin.mach_vm_region;
pub const mach_vm_region_recurse = darwin.mach_vm_region_recurse;
pub const mach_vm_size_t = darwin.mach_vm_size_t;
pub const mach_vm_write = darwin.mach_vm_write;
+pub const natural_t = darwin.natural_t;
pub const os_log_create = darwin.os_log_create;
pub const os_log_type_enabled = darwin.os_log_type_enabled;
pub const os_signpost_enabled = darwin.os_signpost_enabled;
diff --git a/lib/std/compress.zig b/lib/std/compress.zig
index 200489c18a..7cc4a80d33 100644
--- a/lib/std/compress.zig
+++ b/lib/std/compress.zig
@@ -10,10 +10,7 @@ pub const lzma2 = @import("compress/lzma2.zig");
pub const xz = @import("compress/xz.zig");
pub const zstd = @import("compress/zstandard.zig");
-pub fn HashedReader(
- comptime ReaderType: anytype,
- comptime HasherType: anytype,
-) type {
+pub fn HashedReader(ReaderType: type, HasherType: type) type {
return struct {
child_reader: ReaderType,
hasher: HasherType,
@@ -40,10 +37,7 @@ pub fn hashedReader(
return .{ .child_reader = reader, .hasher = hasher };
}
-pub fn HashedWriter(
- comptime WriterType: anytype,
- comptime HasherType: anytype,
-) type {
+pub fn HashedWriter(WriterType: type, HasherType: type) type {
return struct {
child_writer: WriterType,
hasher: HasherType,
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index 8134194380..643dcf731a 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -775,6 +775,7 @@ pub const StackIterator = struct {
}
pub fn deinit(it: *StackIterator) void {
+ it.ma.deinit();
if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit();
}
diff --git a/lib/std/debug/MemoryAccessor.zig b/lib/std/debug/MemoryAccessor.zig
index 9f112262be..5f57ad5853 100644
--- a/lib/std/debug/MemoryAccessor.zig
+++ b/lib/std/debug/MemoryAccessor.zig
@@ -25,6 +25,17 @@ pub const init: MemoryAccessor = .{
},
};
+pub fn deinit(ma: *MemoryAccessor) void {
+ switch (native_os) {
+ .linux => switch (ma.mem.handle) {
+ -2, -1 => {},
+ else => ma.mem.close(),
+ },
+ else => {},
+ }
+ ma.* = undefined;
+}
+
fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
switch (native_os) {
.linux => while (true) switch (ma.mem.handle) {
diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig
index 69c3553ac3..9797a1b896 100644
--- a/lib/std/fs/File.zig
+++ b/lib/std/fs/File.zig
@@ -1156,7 +1156,7 @@ pub fn readToEndAllocOptions(
// The file size returned by stat is used as hint to set the buffer
// size. If the reported size is zero, as it happens on Linux for files
// in /proc, a small buffer is allocated instead.
- const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
+ const initial_cap = @min((if (size > 0) size else 1024), max_bytes) + @intFromBool(optional_sentinel != null);
var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
defer array_list.deinit();
diff --git a/lib/std/heap.zig b/lib/std/heap.zig
index 33f79e265a..df72786f0f 100644
--- a/lib/std/heap.zig
+++ b/lib/std/heap.zig
@@ -18,7 +18,6 @@ pub const GeneralPurposeAllocatorConfig = @import("heap/general_purpose_allocato
pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator;
pub const Check = @import("heap/general_purpose_allocator.zig").Check;
pub const WasmAllocator = @import("heap/WasmAllocator.zig");
-pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
pub const PageAllocator = @import("heap/PageAllocator.zig");
pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
pub const SbrkAllocator = @import("heap/sbrk_allocator.zig").SbrkAllocator;
@@ -223,36 +222,35 @@ fn rawCFree(
c.free(buf.ptr);
}
-/// This allocator makes a syscall directly for every allocation and free.
-/// Thread-safe and lock-free.
-pub const page_allocator = if (@hasDecl(root, "os") and
+/// On operating systems that support memory mapping, this allocator makes a
+/// syscall directly for every allocation and free.
+///
+/// Otherwise, it falls back to the preferred singleton for the target.
+///
+/// Thread-safe.
+pub const page_allocator: Allocator = if (@hasDecl(root, "os") and
@hasDecl(root.os, "heap") and
@hasDecl(root.os.heap, "page_allocator"))
root.os.heap.page_allocator
-else if (builtin.target.isWasm())
- Allocator{
- .ptr = undefined,
- .vtable = &WasmPageAllocator.vtable,
- }
-else if (builtin.target.os.tag == .plan9)
- Allocator{
- .ptr = undefined,
- .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable,
- }
-else
- Allocator{
- .ptr = undefined,
- .vtable = &PageAllocator.vtable,
- };
+else if (builtin.target.isWasm()) .{
+ .ptr = undefined,
+ .vtable = &WasmAllocator.vtable,
+} else if (builtin.target.os.tag == .plan9) .{
+ .ptr = undefined,
+ .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable,
+} else .{
+ .ptr = undefined,
+ .vtable = &PageAllocator.vtable,
+};
/// This allocator is fast, small, and specific to WebAssembly. In the future,
/// this will be the implementation automatically selected by
/// `GeneralPurposeAllocator` when compiling in `ReleaseSmall` mode for wasm32
/// and wasm64 architectures.
/// Until then, it is available here to play with.
-pub const wasm_allocator = Allocator{
+pub const wasm_allocator: Allocator = .{
.ptr = undefined,
- .vtable = &std.heap.WasmAllocator.vtable,
+ .vtable = &WasmAllocator.vtable,
};
/// Verifies that the adjusted length will still map to the full length
@@ -892,6 +890,5 @@ test {
_ = GeneralPurposeAllocator;
if (builtin.target.isWasm()) {
_ = WasmAllocator;
- _ = WasmPageAllocator;
}
}
diff --git a/lib/std/heap/WasmAllocator.zig b/lib/std/heap/WasmAllocator.zig
index 61ad624715..fea6ae5f52 100644
--- a/lib/std/heap/WasmAllocator.zig
+++ b/lib/std/heap/WasmAllocator.zig
@@ -10,11 +10,14 @@ const math = std.math;
comptime {
if (!builtin.target.isWasm()) {
- @compileError("WasmPageAllocator is only available for wasm32 arch");
+ @compileError("only available for wasm32 arch");
+ }
+ if (!builtin.single_threaded) {
+ @compileError("TODO implement support for multi-threaded wasm");
}
}
-pub const vtable = Allocator.VTable{
+pub const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = resize,
.free = free,
diff --git a/lib/std/heap/WasmPageAllocator.zig b/lib/std/heap/WasmPageAllocator.zig
deleted file mode 100644
index ca625e43ed..0000000000
--- a/lib/std/heap/WasmPageAllocator.zig
+++ /dev/null
@@ -1,233 +0,0 @@
-const WasmPageAllocator = @This();
-const std = @import("../std.zig");
-const builtin = @import("builtin");
-const Allocator = std.mem.Allocator;
-const mem = std.mem;
-const maxInt = std.math.maxInt;
-const assert = std.debug.assert;
-
-comptime {
- if (!builtin.target.isWasm()) {
- @compileError("WasmPageAllocator is only available for wasm32 arch");
- }
-}
-
-pub const vtable = Allocator.VTable{
- .alloc = alloc,
- .resize = resize,
- .free = free,
-};
-
-const PageStatus = enum(u1) {
- used = 0,
- free = 1,
-
- pub const none_free: u8 = 0;
-};
-
-const FreeBlock = struct {
- data: []u128,
-
- fn totalPages(self: FreeBlock) usize {
- return self.data.len * 128;
- }
-
- fn isInitialized(self: FreeBlock) bool {
- return self.data.len > 0;
- }
-
- fn getBit(self: FreeBlock, idx: usize) PageStatus {
- const bit = mem.readPackedInt(u1, mem.sliceAsBytes(self.data), idx, .little);
- return @as(PageStatus, @enumFromInt(bit));
- }
-
- fn setBits(self: FreeBlock, start_idx: usize, len: usize, val: PageStatus) void {
- var i: usize = 0;
- const bytes = mem.sliceAsBytes(self.data);
- while (i < len) : (i += 1) {
- mem.writePackedInt(u1, bytes, start_idx + i, @intFromEnum(val), .little);
- }
- }
-
- // Use '0xFFFFFFFF' as a _missing_ sentinel
- // This saves ~50 bytes compared to returning a nullable
-
- // We can guarantee that conventional memory never gets this big,
- // and wasm32 would not be able to address this memory (32 GB > usize).
-
- // Revisit if this is settled: https://github.com/ziglang/zig/issues/3806
- const not_found = maxInt(usize);
-
- fn useRecycled(self: FreeBlock, num_pages: usize, log2_align: u8) usize {
- @branchHint(.cold);
- for (self.data, 0..) |segment, i| {
- const spills_into_next = @as(i128, @bitCast(segment)) < 0;
- const has_enough_bits = @popCount(segment) >= num_pages;
-
- if (!spills_into_next and !has_enough_bits) continue;
-
- var j: usize = i * 128;
- while (j < (i + 1) * 128) : (j += 1) {
- var count: usize = 0;
- while (j + count < self.totalPages() and self.getBit(j + count) == .free) {
- count += 1;
- const addr = j * mem.page_size;
- if (count >= num_pages and mem.isAlignedLog2(addr, log2_align)) {
- self.setBits(j, num_pages, .used);
- return j;
- }
- }
- j += count;
- }
- }
- return not_found;
- }
-
- fn recycle(self: FreeBlock, start_idx: usize, len: usize) void {
- self.setBits(start_idx, len, .free);
- }
-};
-
-var _conventional_data = [_]u128{0} ** 16;
-// Marking `conventional` as const saves ~40 bytes
-const conventional = FreeBlock{ .data = &_conventional_data };
-var extended = FreeBlock{ .data = &[_]u128{} };
-
-fn extendedOffset() usize {
- return conventional.totalPages();
-}
-
-fn nPages(memsize: usize) usize {
- return mem.alignForward(usize, memsize, mem.page_size) / mem.page_size;
-}
-
-fn alloc(ctx: *anyopaque, len: usize, log2_align: u8, ra: usize) ?[*]u8 {
- _ = ctx;
- _ = ra;
- if (len > maxInt(usize) - (mem.page_size - 1)) return null;
- const page_count = nPages(len);
- const page_idx = allocPages(page_count, log2_align) catch return null;
- return @as([*]u8, @ptrFromInt(page_idx * mem.page_size));
-}
-
-fn allocPages(page_count: usize, log2_align: u8) !usize {
- {
- const idx = conventional.useRecycled(page_count, log2_align);
- if (idx != FreeBlock.not_found) {
- return idx;
- }
- }
-
- const idx = extended.useRecycled(page_count, log2_align);
- if (idx != FreeBlock.not_found) {
- return idx + extendedOffset();
- }
-
- const next_page_idx = @wasmMemorySize(0);
- const next_page_addr = next_page_idx * mem.page_size;
- const aligned_addr = mem.alignForwardLog2(next_page_addr, log2_align);
- const drop_page_count = @divExact(aligned_addr - next_page_addr, mem.page_size);
- const result = @wasmMemoryGrow(0, @as(u32, @intCast(drop_page_count + page_count)));
- if (result <= 0)
- return error.OutOfMemory;
- assert(result == next_page_idx);
- const aligned_page_idx = next_page_idx + drop_page_count;
- if (drop_page_count > 0) {
- freePages(next_page_idx, aligned_page_idx);
- }
- return @as(usize, @intCast(aligned_page_idx));
-}
-
-fn freePages(start: usize, end: usize) void {
- if (start < extendedOffset()) {
- conventional.recycle(start, @min(extendedOffset(), end) - start);
- }
- if (end > extendedOffset()) {
- var new_end = end;
- if (!extended.isInitialized()) {
- // Steal the last page from the memory currently being recycled
- // TODO: would it be better if we use the first page instead?
- new_end -= 1;
-
- extended.data = @as([*]u128, @ptrFromInt(new_end * mem.page_size))[0 .. mem.page_size / @sizeOf(u128)];
- // Since this is the first page being freed and we consume it, assume *nothing* is free.
- @memset(extended.data, PageStatus.none_free);
- }
- const clamped_start = @max(extendedOffset(), start);
- extended.recycle(clamped_start - extendedOffset(), new_end - clamped_start);
- }
-}
-
-fn resize(
- ctx: *anyopaque,
- buf: []u8,
- log2_buf_align: u8,
- new_len: usize,
- return_address: usize,
-) bool {
- _ = ctx;
- _ = log2_buf_align;
- _ = return_address;
- const aligned_len = mem.alignForward(usize, buf.len, mem.page_size);
- if (new_len > aligned_len) return false;
- const current_n = nPages(aligned_len);
- const new_n = nPages(new_len);
- if (new_n != current_n) {
- const base = nPages(@intFromPtr(buf.ptr));
- freePages(base + new_n, base + current_n);
- }
- return true;
-}
-
-fn free(
- ctx: *anyopaque,
- buf: []u8,
- log2_buf_align: u8,
- return_address: usize,
-) void {
- _ = ctx;
- _ = log2_buf_align;
- _ = return_address;
- const aligned_len = mem.alignForward(usize, buf.len, mem.page_size);
- const current_n = nPages(aligned_len);
- const base = nPages(@intFromPtr(buf.ptr));
- freePages(base, base + current_n);
-}
-
-test "internals" {
- const page_allocator = std.heap.page_allocator;
- const testing = std.testing;
-
- const conventional_memsize = WasmPageAllocator.conventional.totalPages() * mem.page_size;
- const initial = try page_allocator.alloc(u8, mem.page_size);
- try testing.expect(@intFromPtr(initial.ptr) < conventional_memsize); // If this isn't conventional, the rest of these tests don't make sense. Also we have a serious memory leak in the test suite.
-
- var inplace = try page_allocator.realloc(initial, 1);
- try testing.expectEqual(initial.ptr, inplace.ptr);
- inplace = try page_allocator.realloc(inplace, 4);
- try testing.expectEqual(initial.ptr, inplace.ptr);
- page_allocator.free(inplace);
-
- const reuse = try page_allocator.alloc(u8, 1);
- try testing.expectEqual(initial.ptr, reuse.ptr);
- page_allocator.free(reuse);
-
- // This segment may span conventional and extended which has really complex rules so we're just ignoring it for now.
- const padding = try page_allocator.alloc(u8, conventional_memsize);
- page_allocator.free(padding);
-
- const ext = try page_allocator.alloc(u8, conventional_memsize);
- try testing.expect(@intFromPtr(ext.ptr) >= conventional_memsize);
-
- const use_small = try page_allocator.alloc(u8, 1);
- try testing.expectEqual(initial.ptr, use_small.ptr);
- page_allocator.free(use_small);
-
- inplace = try page_allocator.realloc(ext, 1);
- try testing.expectEqual(ext.ptr, inplace.ptr);
- page_allocator.free(inplace);
-
- const reuse_extended = try page_allocator.alloc(u8, conventional_memsize);
- try testing.expectEqual(ext.ptr, reuse_extended.ptr);
- page_allocator.free(reuse_extended);
-}
diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig
index b760c9d85d..c23f8dcd79 100644
--- a/lib/std/heap/general_purpose_allocator.zig
+++ b/lib/std/heap/general_purpose_allocator.zig
@@ -1171,7 +1171,11 @@ test "shrink" {
}
test "large object - grow" {
- var gpa = GeneralPurposeAllocator(test_config){};
+ if (builtin.target.isWasm()) {
+ // Not expected to pass on targets that do not have memory mapping.
+ return error.SkipZigTest;
+ }
+ var gpa: GeneralPurposeAllocator(test_config) = .{};
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
const allocator = gpa.allocator();
@@ -1231,6 +1235,8 @@ test "shrink large object to large object" {
}
test "shrink large object to large object with larger alignment" {
+ if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731
+
var gpa = GeneralPurposeAllocator(test_config){};
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
const allocator = gpa.allocator();
@@ -1303,6 +1309,8 @@ test "non-page-allocator backing allocator" {
}
test "realloc large object to larger alignment" {
+ if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731
+
var gpa = GeneralPurposeAllocator(test_config){};
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
const allocator = gpa.allocator();
@@ -1344,6 +1352,11 @@ test "realloc large object to larger alignment" {
}
test "large object shrinks to small but allocation fails during shrink" {
+ if (builtin.target.isWasm()) {
+ // Not expected to pass on targets that do not have memory mapping.
+ return error.SkipZigTest;
+ }
+
var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, .{ .fail_index = 3 });
var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = failing_allocator.allocator() };
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig
index 3e9109dd1a..0af1a052b6 100644
--- a/lib/std/math/big/int.zig
+++ b/lib/std/math/big/int.zig
@@ -2175,10 +2175,13 @@ pub const Const = struct {
TargetTooSmall,
};
- /// Convert self to type T.
+ /// Deprecated; use `toInt`.
+ pub const to = toInt;
+
+ /// Convert self to integer type T.
///
/// Returns an error if self cannot be narrowed into the requested type without truncation.
- pub fn to(self: Const, comptime T: type) ConvertError!T {
+ pub fn toInt(self: Const, comptime T: type) ConvertError!T {
switch (@typeInfo(T)) {
.int => |info| {
// Make sure -0 is handled correctly.
@@ -2216,7 +2219,26 @@ pub const Const = struct {
}
}
},
- else => @compileError("cannot convert Const to type " ++ @typeName(T)),
+ else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"),
+ }
+ }
+
+ /// Convert self to float type T.
+ pub fn toFloat(self: Const, comptime T: type) T {
+ if (self.limbs.len == 0) return 0;
+
+ const base = std.math.maxInt(std.math.big.Limb) + 1;
+ var result: f128 = 0;
+ var i: usize = self.limbs.len;
+ while (i != 0) {
+ i -= 1;
+ const limb: f128 = @floatFromInt(self.limbs[i]);
+ result = @mulAdd(f128, base, result, limb);
+ }
+ if (self.positive) {
+ return @floatCast(result);
+ } else {
+ return @floatCast(-result);
}
}
@@ -2775,11 +2797,19 @@ pub const Managed = struct {
pub const ConvertError = Const.ConvertError;
- /// Convert self to type T.
+ /// Deprecated; use `toInt`.
+ pub const to = toInt;
+
+ /// Convert self to integer type T.
///
/// Returns an error if self cannot be narrowed into the requested type without truncation.
- pub fn to(self: Managed, comptime T: type) ConvertError!T {
- return self.toConst().to(T);
+ pub fn toInt(self: Managed, comptime T: type) ConvertError!T {
+ return self.toConst().toInt(T);
+ }
+
+ /// Convert self to float type T.
+ pub fn toFloat(self: Managed, comptime T: type) T {
+ return self.toConst().toFloat(T);
}
/// Set self from the string representation `value`.
diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig
index 2e0ccc96c1..a8907fdae0 100644
--- a/lib/std/math/big/int_test.zig
+++ b/lib/std/math/big/int_test.zig
@@ -53,21 +53,21 @@ test "comptime_int to" {
var a = try Managed.initSet(testing.allocator, 0xefffffff00000001eeeeeeefaaaaaaab);
defer a.deinit();
- try testing.expect((try a.to(u128)) == 0xefffffff00000001eeeeeeefaaaaaaab);
+ try testing.expect((try a.toInt(u128)) == 0xefffffff00000001eeeeeeefaaaaaaab);
}
test "sub-limb to" {
var a = try Managed.initSet(testing.allocator, 10);
defer a.deinit();
- try testing.expect((try a.to(u8)) == 10);
+ try testing.expect((try a.toInt(u8)) == 10);
}
test "set negative minimum" {
var a = try Managed.initSet(testing.allocator, @as(i64, minInt(i64)));
defer a.deinit();
- try testing.expect((try a.to(i64)) == minInt(i64));
+ try testing.expect((try a.toInt(i64)) == minInt(i64));
}
test "set double-width maximum then zero" {
@@ -75,14 +75,14 @@ test "set double-width maximum then zero" {
defer a.deinit();
try a.set(@as(DoubleLimb, 0));
- try testing.expectEqual(@as(DoubleLimb, 0), try a.to(DoubleLimb));
+ try testing.expectEqual(@as(DoubleLimb, 0), try a.toInt(DoubleLimb));
}
test "to target too small error" {
var a = try Managed.initSet(testing.allocator, 0xffffffff);
defer a.deinit();
- try testing.expectError(error.TargetTooSmall, a.to(u8));
+ try testing.expectError(error.TargetTooSmall, a.toInt(u8));
}
test "normalize" {
@@ -191,28 +191,28 @@ test "bitcount/to" {
try a.set(0);
try testing.expect(a.bitCountTwosComp() == 0);
- try testing.expect((try a.to(u0)) == 0);
- try testing.expect((try a.to(i0)) == 0);
+ try testing.expect((try a.toInt(u0)) == 0);
+ try testing.expect((try a.toInt(i0)) == 0);
try a.set(-1);
try testing.expect(a.bitCountTwosComp() == 1);
- try testing.expect((try a.to(i1)) == -1);
+ try testing.expect((try a.toInt(i1)) == -1);
try a.set(-8);
try testing.expect(a.bitCountTwosComp() == 4);
- try testing.expect((try a.to(i4)) == -8);
+ try testing.expect((try a.toInt(i4)) == -8);
try a.set(127);
try testing.expect(a.bitCountTwosComp() == 7);
- try testing.expect((try a.to(u7)) == 127);
+ try testing.expect((try a.toInt(u7)) == 127);
try a.set(-128);
try testing.expect(a.bitCountTwosComp() == 8);
- try testing.expect((try a.to(i8)) == -128);
+ try testing.expect((try a.toInt(i8)) == -128);
try a.set(-129);
try testing.expect(a.bitCountTwosComp() == 9);
- try testing.expect((try a.to(i9)) == -129);
+ try testing.expect((try a.toInt(i9)) == -129);
}
test "fits" {
@@ -248,7 +248,7 @@ test "string set" {
defer a.deinit();
try a.setString(10, "120317241209124781241290847124");
- try testing.expect((try a.to(u128)) == 120317241209124781241290847124);
+ try testing.expect((try a.toInt(u128)) == 120317241209124781241290847124);
}
test "string negative" {
@@ -256,7 +256,7 @@ test "string negative" {
defer a.deinit();
try a.setString(10, "-1023");
- try testing.expect((try a.to(i32)) == -1023);
+ try testing.expect((try a.toInt(i32)) == -1023);
}
test "string set number with underscores" {
@@ -264,7 +264,7 @@ test "string set number with underscores" {
defer a.deinit();
try a.setString(10, "__1_2_0_3_1_7_2_4_1_2_0_____9_1__2__4_7_8_1_2_4_1_2_9_0_8_4_7_1_2_4___");
- try testing.expect((try a.to(u128)) == 120317241209124781241290847124);
+ try testing.expect((try a.toInt(u128)) == 120317241209124781241290847124);
}
test "string set case insensitive number" {
@@ -272,7 +272,7 @@ test "string set case insensitive number" {
defer a.deinit();
try a.setString(16, "aB_cD_eF");
- try testing.expect((try a.to(u32)) == 0xabcdef);
+ try testing.expect((try a.toInt(u32)) == 0xabcdef);
}
test "string set bad char error" {
@@ -306,11 +306,11 @@ fn testTwosComplementLimit(comptime T: type) !void {
try a.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
const max: T = maxInt(T);
- try testing.expect(max == try a.to(T));
+ try testing.expect(max == try a.toInt(T));
try a.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
const min: T = minInt(T);
- try testing.expect(min == try a.to(T));
+ try testing.expect(min == try a.toInt(T));
}
test "string to" {
@@ -381,12 +381,12 @@ test "clone" {
var b = try a.clone();
defer b.deinit();
- try testing.expect((try a.to(u32)) == 1234);
- try testing.expect((try b.to(u32)) == 1234);
+ try testing.expect((try a.toInt(u32)) == 1234);
+ try testing.expect((try b.toInt(u32)) == 1234);
try a.set(77);
- try testing.expect((try a.to(u32)) == 77);
- try testing.expect((try b.to(u32)) == 1234);
+ try testing.expect((try a.toInt(u32)) == 77);
+ try testing.expect((try b.toInt(u32)) == 1234);
}
test "swap" {
@@ -395,20 +395,20 @@ test "swap" {
var b = try Managed.initSet(testing.allocator, 5678);
defer b.deinit();
- try testing.expect((try a.to(u32)) == 1234);
- try testing.expect((try b.to(u32)) == 5678);
+ try testing.expect((try a.toInt(u32)) == 1234);
+ try testing.expect((try b.toInt(u32)) == 5678);
a.swap(&b);
- try testing.expect((try a.to(u32)) == 5678);
- try testing.expect((try b.to(u32)) == 1234);
+ try testing.expect((try a.toInt(u32)) == 5678);
+ try testing.expect((try b.toInt(u32)) == 1234);
}
test "to negative" {
var a = try Managed.initSet(testing.allocator, -10);
defer a.deinit();
- try testing.expect((try a.to(i32)) == -10);
+ try testing.expect((try a.toInt(i32)) == -10);
}
test "compare" {
@@ -466,10 +466,10 @@ test "abs" {
defer a.deinit();
a.abs();
- try testing.expect((try a.to(u32)) == 5);
+ try testing.expect((try a.toInt(u32)) == 5);
a.abs();
- try testing.expect((try a.to(u32)) == 5);
+ try testing.expect((try a.toInt(u32)) == 5);
}
test "negate" {
@@ -477,10 +477,10 @@ test "negate" {
defer a.deinit();
a.negate();
- try testing.expect((try a.to(i32)) == -5);
+ try testing.expect((try a.toInt(i32)) == -5);
a.negate();
- try testing.expect((try a.to(i32)) == 5);
+ try testing.expect((try a.toInt(i32)) == 5);
}
test "add single-single" {
@@ -493,7 +493,7 @@ test "add single-single" {
defer c.deinit();
try c.add(&a, &b);
- try testing.expect((try c.to(u32)) == 55);
+ try testing.expect((try c.toInt(u32)) == 55);
}
test "add multi-single" {
@@ -506,10 +506,10 @@ test "add multi-single" {
defer c.deinit();
try c.add(&a, &b);
- try testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2);
+ try testing.expect((try c.toInt(DoubleLimb)) == maxInt(Limb) + 2);
try c.add(&b, &a);
- try testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2);
+ try testing.expect((try c.toInt(DoubleLimb)) == maxInt(Limb) + 2);
}
test "add multi-multi" {
@@ -527,7 +527,7 @@ test "add multi-multi" {
defer c.deinit();
try c.add(&a, &b);
- try testing.expect((try c.to(u128)) == op1 + op2);
+ try testing.expect((try c.toInt(u128)) == op1 + op2);
}
test "add zero-zero" {
@@ -540,7 +540,7 @@ test "add zero-zero" {
defer c.deinit();
try c.add(&a, &b);
- try testing.expect((try c.to(u32)) == 0);
+ try testing.expect((try c.toInt(u32)) == 0);
}
test "add alias multi-limb nonzero-zero" {
@@ -552,7 +552,7 @@ test "add alias multi-limb nonzero-zero" {
try a.add(&a, &b);
- try testing.expect((try a.to(u128)) == op1);
+ try testing.expect((try a.toInt(u128)) == op1);
}
test "add sign" {
@@ -569,16 +569,16 @@ test "add sign" {
defer neg_two.deinit();
try a.add(&one, &two);
- try testing.expect((try a.to(i32)) == 3);
+ try testing.expect((try a.toInt(i32)) == 3);
try a.add(&neg_one, &two);
- try testing.expect((try a.to(i32)) == 1);
+ try testing.expect((try a.toInt(i32)) == 1);
try a.add(&one, &neg_two);
- try testing.expect((try a.to(i32)) == -1);
+ try testing.expect((try a.toInt(i32)) == -1);
try a.add(&neg_one, &neg_two);
- try testing.expect((try a.to(i32)) == -3);
+ try testing.expect((try a.toInt(i32)) == -3);
}
test "add comptime scalar" {
@@ -589,7 +589,7 @@ test "add comptime scalar" {
defer b.deinit();
try b.addScalar(&a, 5);
- try testing.expect((try b.to(u32)) == 55);
+ try testing.expect((try b.toInt(u32)) == 55);
}
test "add scalar" {
@@ -600,7 +600,7 @@ test "add scalar" {
defer b.deinit();
try b.addScalar(&a, @as(u32, 31));
- try testing.expect((try b.to(u32)) == 154);
+ try testing.expect((try b.toInt(u32)) == 154);
}
test "addWrap single-single, unsigned" {
@@ -613,7 +613,7 @@ test "addWrap single-single, unsigned" {
const wrapped = try a.addWrap(&a, &b, .unsigned, 17);
try testing.expect(wrapped);
- try testing.expect((try a.to(u17)) == 9);
+ try testing.expect((try a.toInt(u17)) == 9);
}
test "subWrap single-single, unsigned" {
@@ -626,7 +626,7 @@ test "subWrap single-single, unsigned" {
const wrapped = try a.subWrap(&a, &b, .unsigned, 17);
try testing.expect(wrapped);
- try testing.expect((try a.to(u17)) == 1);
+ try testing.expect((try a.toInt(u17)) == 1);
}
test "addWrap multi-multi, unsigned, limb aligned" {
@@ -639,7 +639,7 @@ test "addWrap multi-multi, unsigned, limb aligned" {
const wrapped = try a.addWrap(&a, &b, .unsigned, @bitSizeOf(DoubleLimb));
try testing.expect(wrapped);
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1);
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) - 1);
}
test "subWrap single-multi, unsigned, limb aligned" {
@@ -652,7 +652,7 @@ test "subWrap single-multi, unsigned, limb aligned" {
const wrapped = try a.subWrap(&a, &b, .unsigned, @bitSizeOf(DoubleLimb));
try testing.expect(wrapped);
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88);
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) - 88);
}
test "addWrap single-single, signed" {
@@ -665,7 +665,7 @@ test "addWrap single-single, signed" {
const wrapped = try a.addWrap(&a, &b, .signed, @bitSizeOf(i21));
try testing.expect(wrapped);
- try testing.expect((try a.to(i21)) == minInt(i21));
+ try testing.expect((try a.toInt(i21)) == minInt(i21));
}
test "subWrap single-single, signed" {
@@ -678,7 +678,7 @@ test "subWrap single-single, signed" {
const wrapped = try a.subWrap(&a, &b, .signed, @bitSizeOf(i21));
try testing.expect(wrapped);
- try testing.expect((try a.to(i21)) == maxInt(i21));
+ try testing.expect((try a.toInt(i21)) == maxInt(i21));
}
test "addWrap multi-multi, signed, limb aligned" {
@@ -691,7 +691,7 @@ test "addWrap multi-multi, signed, limb aligned" {
const wrapped = try a.addWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb));
try testing.expect(wrapped);
- try testing.expect((try a.to(SignedDoubleLimb)) == -2);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -2);
}
test "subWrap single-multi, signed, limb aligned" {
@@ -704,7 +704,7 @@ test "subWrap single-multi, signed, limb aligned" {
const wrapped = try a.subWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb));
try testing.expect(wrapped);
- try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
}
test "addSat single-single, unsigned" {
@@ -716,7 +716,7 @@ test "addSat single-single, unsigned" {
try a.addSat(&a, &b, .unsigned, 17);
- try testing.expect((try a.to(u17)) == maxInt(u17));
+ try testing.expect((try a.toInt(u17)) == maxInt(u17));
}
test "subSat single-single, unsigned" {
@@ -728,7 +728,7 @@ test "subSat single-single, unsigned" {
try a.subSat(&a, &b, .unsigned, 17);
- try testing.expect((try a.to(u17)) == 0);
+ try testing.expect((try a.toInt(u17)) == 0);
}
test "addSat multi-multi, unsigned, limb aligned" {
@@ -740,7 +740,7 @@ test "addSat multi-multi, unsigned, limb aligned" {
try a.addSat(&a, &b, .unsigned, @bitSizeOf(DoubleLimb));
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb));
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb));
}
test "subSat single-multi, unsigned, limb aligned" {
@@ -752,7 +752,7 @@ test "subSat single-multi, unsigned, limb aligned" {
try a.subSat(&a, &b, .unsigned, @bitSizeOf(DoubleLimb));
- try testing.expect((try a.to(DoubleLimb)) == 0);
+ try testing.expect((try a.toInt(DoubleLimb)) == 0);
}
test "addSat single-single, signed" {
@@ -764,7 +764,7 @@ test "addSat single-single, signed" {
try a.addSat(&a, &b, .signed, @bitSizeOf(i14));
- try testing.expect((try a.to(i14)) == maxInt(i14));
+ try testing.expect((try a.toInt(i14)) == maxInt(i14));
}
test "subSat single-single, signed" {
@@ -776,7 +776,7 @@ test "subSat single-single, signed" {
try a.subSat(&a, &b, .signed, @bitSizeOf(i21));
- try testing.expect((try a.to(i21)) == minInt(i21));
+ try testing.expect((try a.toInt(i21)) == minInt(i21));
}
test "addSat multi-multi, signed, limb aligned" {
@@ -788,7 +788,7 @@ test "addSat multi-multi, signed, limb aligned" {
try a.addSat(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
}
test "subSat single-multi, signed, limb aligned" {
@@ -800,7 +800,7 @@ test "subSat single-multi, signed, limb aligned" {
try a.subSat(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == minInt(SignedDoubleLimb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == minInt(SignedDoubleLimb));
}
test "sub single-single" {
@@ -813,7 +813,7 @@ test "sub single-single" {
defer c.deinit();
try c.sub(&a, &b);
- try testing.expect((try c.to(u32)) == 45);
+ try testing.expect((try c.toInt(u32)) == 45);
}
test "sub multi-single" {
@@ -826,7 +826,7 @@ test "sub multi-single" {
defer c.deinit();
try c.sub(&a, &b);
- try testing.expect((try c.to(Limb)) == maxInt(Limb));
+ try testing.expect((try c.toInt(Limb)) == maxInt(Limb));
}
test "sub multi-multi" {
@@ -843,7 +843,7 @@ test "sub multi-multi" {
defer c.deinit();
try c.sub(&a, &b);
- try testing.expect((try c.to(u128)) == op1 - op2);
+ try testing.expect((try c.toInt(u128)) == op1 - op2);
}
test "sub equal" {
@@ -856,7 +856,7 @@ test "sub equal" {
defer c.deinit();
try c.sub(&a, &b);
- try testing.expect((try c.to(u32)) == 0);
+ try testing.expect((try c.toInt(u32)) == 0);
}
test "sub sign" {
@@ -873,19 +873,19 @@ test "sub sign" {
defer neg_two.deinit();
try a.sub(&one, &two);
- try testing.expect((try a.to(i32)) == -1);
+ try testing.expect((try a.toInt(i32)) == -1);
try a.sub(&neg_one, &two);
- try testing.expect((try a.to(i32)) == -3);
+ try testing.expect((try a.toInt(i32)) == -3);
try a.sub(&one, &neg_two);
- try testing.expect((try a.to(i32)) == 3);
+ try testing.expect((try a.toInt(i32)) == 3);
try a.sub(&neg_one, &neg_two);
- try testing.expect((try a.to(i32)) == 1);
+ try testing.expect((try a.toInt(i32)) == 1);
try a.sub(&neg_two, &neg_one);
- try testing.expect((try a.to(i32)) == -1);
+ try testing.expect((try a.toInt(i32)) == -1);
}
test "mul single-single" {
@@ -898,7 +898,7 @@ test "mul single-single" {
defer c.deinit();
try c.mul(&a, &b);
- try testing.expect((try c.to(u64)) == 250);
+ try testing.expect((try c.toInt(u64)) == 250);
}
test "mul multi-single" {
@@ -911,7 +911,7 @@ test "mul multi-single" {
defer c.deinit();
try c.mul(&a, &b);
- try testing.expect((try c.to(DoubleLimb)) == 2 * maxInt(Limb));
+ try testing.expect((try c.toInt(DoubleLimb)) == 2 * maxInt(Limb));
}
test "mul multi-multi" {
@@ -930,7 +930,7 @@ test "mul multi-multi" {
defer c.deinit();
try c.mul(&a, &b);
- try testing.expect((try c.to(u256)) == op1 * op2);
+ try testing.expect((try c.toInt(u256)) == op1 * op2);
}
test "mul alias r with a" {
@@ -941,7 +941,7 @@ test "mul alias r with a" {
try a.mul(&a, &b);
- try testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb));
+ try testing.expect((try a.toInt(DoubleLimb)) == 2 * maxInt(Limb));
}
test "mul alias r with b" {
@@ -952,7 +952,7 @@ test "mul alias r with b" {
try a.mul(&b, &a);
- try testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb));
+ try testing.expect((try a.toInt(DoubleLimb)) == 2 * maxInt(Limb));
}
test "mul alias r with a and b" {
@@ -961,7 +961,7 @@ test "mul alias r with a and b" {
try a.mul(&a, &a);
- try testing.expect((try a.to(DoubleLimb)) == maxInt(Limb) * maxInt(Limb));
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(Limb) * maxInt(Limb));
}
test "mul a*0" {
@@ -974,7 +974,7 @@ test "mul a*0" {
defer c.deinit();
try c.mul(&a, &b);
- try testing.expect((try c.to(u32)) == 0);
+ try testing.expect((try c.toInt(u32)) == 0);
}
test "mul 0*0" {
@@ -987,7 +987,7 @@ test "mul 0*0" {
defer c.deinit();
try c.mul(&a, &b);
- try testing.expect((try c.to(u32)) == 0);
+ try testing.expect((try c.toInt(u32)) == 0);
}
test "mul large" {
@@ -1021,7 +1021,7 @@ test "mulWrap single-single unsigned" {
defer c.deinit();
try c.mulWrap(&a, &b, .unsigned, 17);
- try testing.expect((try c.to(u17)) == 59836);
+ try testing.expect((try c.toInt(u17)) == 59836);
}
test "mulWrap single-single signed" {
@@ -1034,7 +1034,7 @@ test "mulWrap single-single signed" {
defer c.deinit();
try c.mulWrap(&a, &b, .signed, 17);
- try testing.expect((try c.to(i17)) == -59836);
+ try testing.expect((try c.toInt(i17)) == -59836);
}
test "mulWrap multi-multi unsigned" {
@@ -1053,7 +1053,7 @@ test "mulWrap multi-multi unsigned" {
defer c.deinit();
try c.mulWrap(&a, &b, .unsigned, 65);
- try testing.expect((try c.to(u256)) == (op1 * op2) & ((1 << 65) - 1));
+ try testing.expect((try c.toInt(u256)) == (op1 * op2) & ((1 << 65) - 1));
}
test "mulWrap multi-multi signed" {
@@ -1071,7 +1071,7 @@ test "mulWrap multi-multi signed" {
defer c.deinit();
try c.mulWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try c.to(SignedDoubleLimb)) == minInt(SignedDoubleLimb) + 2);
+ try testing.expect((try c.toInt(SignedDoubleLimb)) == minInt(SignedDoubleLimb) + 2);
}
test "mulWrap large" {
@@ -1110,8 +1110,8 @@ test "div single-half no rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u32)) == 10);
- try testing.expect((try r.to(u32)) == 0);
+ try testing.expect((try q.toInt(u32)) == 10);
+ try testing.expect((try r.toInt(u32)) == 0);
}
test "div single-half with rem" {
@@ -1126,8 +1126,8 @@ test "div single-half with rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u32)) == 9);
- try testing.expect((try r.to(u32)) == 4);
+ try testing.expect((try q.toInt(u32)) == 9);
+ try testing.expect((try r.toInt(u32)) == 4);
}
test "div single-single no rem" {
@@ -1143,8 +1143,8 @@ test "div single-single no rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u32)) == 131072);
- try testing.expect((try r.to(u32)) == 0);
+ try testing.expect((try q.toInt(u32)) == 131072);
+ try testing.expect((try r.toInt(u32)) == 0);
}
test "div single-single with rem" {
@@ -1159,8 +1159,8 @@ test "div single-single with rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u64)) == 131072);
- try testing.expect((try r.to(u64)) == 8589934592);
+ try testing.expect((try q.toInt(u64)) == 131072);
+ try testing.expect((try r.toInt(u64)) == 8589934592);
}
test "div multi-single no rem" {
@@ -1179,8 +1179,8 @@ test "div multi-single no rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u64)) == op1 / op2);
- try testing.expect((try r.to(u64)) == 0);
+ try testing.expect((try q.toInt(u64)) == op1 / op2);
+ try testing.expect((try r.toInt(u64)) == 0);
}
test "div multi-single with rem" {
@@ -1199,8 +1199,8 @@ test "div multi-single with rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u64)) == op1 / op2);
- try testing.expect((try r.to(u64)) == 3);
+ try testing.expect((try q.toInt(u64)) == op1 / op2);
+ try testing.expect((try r.toInt(u64)) == 3);
}
test "div multi>2-single" {
@@ -1219,8 +1219,8 @@ test "div multi>2-single" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == op1 / op2);
- try testing.expect((try r.to(u32)) == 0x3e4e);
+ try testing.expect((try q.toInt(u128)) == op1 / op2);
+ try testing.expect((try r.toInt(u32)) == 0x3e4e);
}
test "div single-single q < r" {
@@ -1235,8 +1235,8 @@ test "div single-single q < r" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u64)) == 0);
- try testing.expect((try r.to(u64)) == 0x0078f432);
+ try testing.expect((try q.toInt(u64)) == 0);
+ try testing.expect((try r.toInt(u64)) == 0x0078f432);
}
test "div single-single q == r" {
@@ -1251,8 +1251,8 @@ test "div single-single q == r" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u64)) == 1);
- try testing.expect((try r.to(u64)) == 0);
+ try testing.expect((try q.toInt(u64)) == 1);
+ try testing.expect((try r.toInt(u64)) == 0);
}
test "div q=0 alias" {
@@ -1263,8 +1263,8 @@ test "div q=0 alias" {
try Managed.divTrunc(&a, &b, &a, &b);
- try testing.expect((try a.to(u64)) == 0);
- try testing.expect((try b.to(u64)) == 3);
+ try testing.expect((try a.toInt(u64)) == 0);
+ try testing.expect((try b.toInt(u64)) == 3);
}
test "div multi-multi q < r" {
@@ -1283,8 +1283,8 @@ test "div multi-multi q < r" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0);
- try testing.expect((try r.to(u128)) == op1);
+ try testing.expect((try q.toInt(u128)) == 0);
+ try testing.expect((try r.toInt(u128)) == op1);
}
test "div trunc single-single +/+" {
@@ -1307,8 +1307,8 @@ test "div trunc single-single +/+" {
const eq = @divTrunc(u, v);
const er = @mod(u, v);
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div trunc single-single -/+" {
@@ -1331,8 +1331,8 @@ test "div trunc single-single -/+" {
const eq = -1;
const er = -2;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div trunc single-single +/-" {
@@ -1355,8 +1355,8 @@ test "div trunc single-single +/-" {
const eq = -1;
const er = 2;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div trunc single-single -/-" {
@@ -1379,8 +1379,8 @@ test "div trunc single-single -/-" {
const eq = 1;
const er = -2;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "divTrunc #15535" {
@@ -1417,7 +1417,7 @@ test "divFloor #10932" {
const ress = try res.toString(testing.allocator, 16, .lower);
defer testing.allocator.free(ress);
try testing.expect(std.mem.eql(u8, ress, "194bd136316c046d070b763396297bf8869a605030216b52597015902a172b2a752f62af1568dcd431602f03725bfa62b0be71ae86616210972c0126e173503011ca48c5747ff066d159c95e46b69cbb14c8fc0bd2bf0919f921be96463200000000000000000000000000000000000000000000000000000000000000000000000000000000"));
- try testing.expect((try mod.to(i32)) == 0);
+ try testing.expect((try mod.toInt(i32)) == 0);
}
test "divFloor #11166" {
@@ -1482,7 +1482,7 @@ test "bitAnd #10932" {
try res.bitAnd(&a, &b);
- try testing.expect((try res.to(i32)) == 0);
+ try testing.expect((try res.toInt(i32)) == 0);
}
test "bit And #19235" {
@@ -1495,7 +1495,7 @@ test "bit And #19235" {
try r.bitAnd(&a, &b);
- try testing.expect((try r.to(i128)) == 0x10000000000000000);
+ try testing.expect((try r.toInt(i128)) == 0x10000000000000000);
}
test "div floor single-single +/+" {
@@ -1518,8 +1518,8 @@ test "div floor single-single +/+" {
const eq = 1;
const er = 2;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div floor single-single -/+" {
@@ -1542,8 +1542,8 @@ test "div floor single-single -/+" {
const eq = -2;
const er = 1;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div floor single-single +/-" {
@@ -1566,8 +1566,8 @@ test "div floor single-single +/-" {
const eq = -2;
const er = -1;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div floor single-single -/-" {
@@ -1590,8 +1590,8 @@ test "div floor single-single -/-" {
const eq = 1;
const er = -2;
- try testing.expect((try q.to(i32)) == eq);
- try testing.expect((try r.to(i32)) == er);
+ try testing.expect((try q.toInt(i32)) == eq);
+ try testing.expect((try r.toInt(i32)) == er);
}
test "div floor no remainder negative quotient" {
@@ -1609,8 +1609,8 @@ test "div floor no remainder negative quotient" {
defer r.deinit();
try Managed.divFloor(&q, &r, &a, &b);
- try testing.expect((try q.to(i32)) == -0x80000000);
- try testing.expect((try r.to(i32)) == 0);
+ try testing.expect((try q.toInt(i32)) == -0x80000000);
+ try testing.expect((try r.toInt(i32)) == 0);
}
test "div floor negative close to zero" {
@@ -1628,8 +1628,8 @@ test "div floor negative close to zero" {
defer r.deinit();
try Managed.divFloor(&q, &r, &a, &b);
- try testing.expect((try q.to(i32)) == -1);
- try testing.expect((try r.to(i32)) == 10);
+ try testing.expect((try q.toInt(i32)) == -1);
+ try testing.expect((try r.toInt(i32)) == 10);
}
test "div floor positive close to zero" {
@@ -1647,8 +1647,8 @@ test "div floor positive close to zero" {
defer r.deinit();
try Managed.divFloor(&q, &r, &a, &b);
- try testing.expect((try q.to(i32)) == 0);
- try testing.expect((try r.to(i32)) == 10);
+ try testing.expect((try q.toInt(i32)) == 0);
+ try testing.expect((try r.toInt(i32)) == 10);
}
test "div multi-multi with rem" {
@@ -1665,8 +1665,8 @@ test "div multi-multi with rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b);
- try testing.expect((try r.to(u128)) == 0x28de0acacd806823638);
+ try testing.expect((try q.toInt(u128)) == 0xe38f38e39161aaabd03f0f1b);
+ try testing.expect((try r.toInt(u128)) == 0x28de0acacd806823638);
}
test "div multi-multi no rem" {
@@ -1683,8 +1683,8 @@ test "div multi-multi no rem" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b);
- try testing.expect((try r.to(u128)) == 0);
+ try testing.expect((try q.toInt(u128)) == 0xe38f38e39161aaabd03f0f1b);
+ try testing.expect((try r.toInt(u128)) == 0);
}
test "div multi-multi (2 branch)" {
@@ -1701,8 +1701,8 @@ test "div multi-multi (2 branch)" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0x10000000000000000);
- try testing.expect((try r.to(u128)) == 0x44444443444444431111111111111111);
+ try testing.expect((try q.toInt(u128)) == 0x10000000000000000);
+ try testing.expect((try r.toInt(u128)) == 0x44444443444444431111111111111111);
}
test "div multi-multi (3.1/3.3 branch)" {
@@ -1719,8 +1719,8 @@ test "div multi-multi (3.1/3.3 branch)" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0xfffffffffffffffffff);
- try testing.expect((try r.to(u256)) == 0x1111111111111111111110b12222222222222222282);
+ try testing.expect((try q.toInt(u128)) == 0xfffffffffffffffffff);
+ try testing.expect((try r.toInt(u256)) == 0x1111111111111111111110b12222222222222222282);
}
test "div multi-single zero-limb trailing" {
@@ -1757,7 +1757,7 @@ test "div multi-multi zero-limb trailing (with rem)" {
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0x10000000000000000);
+ try testing.expect((try q.toInt(u128)) == 0x10000000000000000);
const rs = try r.toString(testing.allocator, 16, .lower);
defer testing.allocator.free(rs);
@@ -1778,7 +1778,7 @@ test "div multi-multi zero-limb trailing (with rem) and dividend zero-limb count
defer r.deinit();
try Managed.divTrunc(&q, &r, &a, &b);
- try testing.expect((try q.to(u128)) == 0x1);
+ try testing.expect((try q.toInt(u128)) == 0x1);
const rs = try r.toString(testing.allocator, 16, .lower);
defer testing.allocator.free(rs);
@@ -1862,7 +1862,7 @@ test "truncate single unsigned" {
try a.truncate(&a, .unsigned, 17);
- try testing.expect((try a.to(u17)) == maxInt(u17));
+ try testing.expect((try a.toInt(u17)) == maxInt(u17));
}
test "truncate single signed" {
@@ -1871,7 +1871,7 @@ test "truncate single signed" {
try a.truncate(&a, .signed, 17);
- try testing.expect((try a.to(i17)) == minInt(i17));
+ try testing.expect((try a.toInt(i17)) == minInt(i17));
}
test "truncate multi to single unsigned" {
@@ -1880,7 +1880,7 @@ test "truncate multi to single unsigned" {
try a.truncate(&a, .unsigned, 27);
- try testing.expect((try a.to(u27)) == 0x2BC_DEF0);
+ try testing.expect((try a.toInt(u27)) == 0x2BC_DEF0);
}
test "truncate multi to single signed" {
@@ -1889,7 +1889,7 @@ test "truncate multi to single signed" {
try a.truncate(&a, .signed, @bitSizeOf(i11));
- try testing.expect((try a.to(i11)) == minInt(i11));
+ try testing.expect((try a.toInt(i11)) == minInt(i11));
}
test "truncate multi to multi unsigned" {
@@ -1901,7 +1901,7 @@ test "truncate multi to multi unsigned" {
try a.truncate(&a, .unsigned, bits - 1);
- try testing.expect((try a.to(Int)) == maxInt(Int));
+ try testing.expect((try a.toInt(Int)) == maxInt(Int));
}
test "truncate multi to multi signed" {
@@ -1910,7 +1910,7 @@ test "truncate multi to multi signed" {
try a.truncate(&a, .signed, @bitSizeOf(Limb) + 1);
- try testing.expect((try a.to(std.meta.Int(.signed, @bitSizeOf(Limb) + 1))) == -1 << @bitSizeOf(Limb));
+ try testing.expect((try a.toInt(std.meta.Int(.signed, @bitSizeOf(Limb) + 1))) == -1 << @bitSizeOf(Limb));
}
test "truncate negative multi to single" {
@@ -1919,7 +1919,7 @@ test "truncate negative multi to single" {
try a.truncate(&a, .signed, @bitSizeOf(i17));
- try testing.expect((try a.to(i17)) == 0);
+ try testing.expect((try a.toInt(i17)) == 0);
}
test "truncate multi unsigned many" {
@@ -1931,7 +1931,7 @@ test "truncate multi unsigned many" {
defer b.deinit();
try b.truncate(&a, .signed, @bitSizeOf(i1));
- try testing.expect((try b.to(i1)) == 0);
+ try testing.expect((try b.toInt(i1)) == 0);
}
test "saturate single signed positive" {
@@ -1940,7 +1940,7 @@ test "saturate single signed positive" {
try a.saturate(&a, .signed, 17);
- try testing.expect((try a.to(i17)) == maxInt(i17));
+ try testing.expect((try a.toInt(i17)) == maxInt(i17));
}
test "saturate single signed negative" {
@@ -1949,7 +1949,7 @@ test "saturate single signed negative" {
try a.saturate(&a, .signed, 17);
- try testing.expect((try a.to(i17)) == minInt(i17));
+ try testing.expect((try a.toInt(i17)) == minInt(i17));
}
test "saturate single signed" {
@@ -1958,7 +1958,7 @@ test "saturate single signed" {
try a.saturate(&a, .signed, 17);
- try testing.expect((try a.to(i17)) == maxInt(i17) - 1);
+ try testing.expect((try a.toInt(i17)) == maxInt(i17) - 1);
}
test "saturate multi signed" {
@@ -1967,7 +1967,7 @@ test "saturate multi signed" {
try a.saturate(&a, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
}
test "saturate single unsigned" {
@@ -1976,7 +1976,7 @@ test "saturate single unsigned" {
try a.saturate(&a, .unsigned, 23);
- try testing.expect((try a.to(u23)) == maxInt(u23));
+ try testing.expect((try a.toInt(u23)) == maxInt(u23));
}
test "saturate multi unsigned zero" {
@@ -1994,7 +1994,7 @@ test "saturate multi unsigned" {
try a.saturate(&a, .unsigned, @bitSizeOf(DoubleLimb));
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb));
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb));
}
test "shift-right single" {
@@ -2002,7 +2002,7 @@ test "shift-right single" {
defer a.deinit();
try a.shiftRight(&a, 16);
- try testing.expect((try a.to(u32)) == 0xffff);
+ try testing.expect((try a.toInt(u32)) == 0xffff);
}
test "shift-right multi" {
@@ -2010,7 +2010,7 @@ test "shift-right multi" {
defer a.deinit();
try a.shiftRight(&a, 67);
- try testing.expect((try a.to(u64)) == 0x1fffe0001dddc222);
+ try testing.expect((try a.toInt(u64)) == 0x1fffe0001dddc222);
try a.set(0xffff0000eeee1111dddd2222cccc3333);
try a.shiftRight(&a, 63);
@@ -2037,7 +2037,7 @@ test "shift-left single" {
defer a.deinit();
try a.shiftLeft(&a, 16);
- try testing.expect((try a.to(u64)) == 0xffff0000);
+ try testing.expect((try a.toInt(u64)) == 0xffff0000);
}
test "shift-left multi" {
@@ -2045,7 +2045,7 @@ test "shift-left multi" {
defer a.deinit();
try a.shiftLeft(&a, 67);
- try testing.expect((try a.to(u128)) == 0xffff0000eeee11100000000000000000);
+ try testing.expect((try a.toInt(u128)) == 0xffff0000eeee11100000000000000000);
}
test "shift-right negative" {
@@ -2055,43 +2055,43 @@ test "shift-right negative" {
var arg = try Managed.initSet(testing.allocator, -20);
defer arg.deinit();
try a.shiftRight(&arg, 2);
- try testing.expect((try a.to(i32)) == -5); // -20 >> 2 == -5
+ try testing.expect((try a.toInt(i32)) == -5); // -20 >> 2 == -5
var arg2 = try Managed.initSet(testing.allocator, -5);
defer arg2.deinit();
try a.shiftRight(&arg2, 10);
- try testing.expect((try a.to(i32)) == -1); // -5 >> 10 == -1
+ try testing.expect((try a.toInt(i32)) == -1); // -5 >> 10 == -1
var arg3 = try Managed.initSet(testing.allocator, -10);
defer arg3.deinit();
try a.shiftRight(&arg3, 1232);
- try testing.expect((try a.to(i32)) == -1); // -10 >> 1232 == -1
+ try testing.expect((try a.toInt(i32)) == -1); // -10 >> 1232 == -1
var arg4 = try Managed.initSet(testing.allocator, -5);
defer arg4.deinit();
try a.shiftRight(&arg4, 2);
- try testing.expect(try a.to(i32) == -2); // -5 >> 2 == -2
+ try testing.expect(try a.toInt(i32) == -2); // -5 >> 2 == -2
var arg5 = try Managed.initSet(testing.allocator, -0xffff0000eeee1111dddd2222cccc3333);
defer arg5.deinit();
try a.shiftRight(&arg5, 67);
- try testing.expect(try a.to(i64) == -0x1fffe0001dddc223);
+ try testing.expect(try a.toInt(i64) == -0x1fffe0001dddc223);
var arg6 = try Managed.initSet(testing.allocator, -0x1ffffffffffffffff);
defer arg6.deinit();
try a.shiftRight(&arg6, 1);
try a.shiftRight(&a, 1);
a.setSign(true);
- try testing.expect(try a.to(u64) == 0x8000000000000000);
+ try testing.expect(try a.toInt(u64) == 0x8000000000000000);
var arg7 = try Managed.initSet(testing.allocator, -32767);
defer arg7.deinit();
a.setSign(false);
try a.shiftRight(&arg7, 4);
- try testing.expect(try a.to(i16) == -2048);
+ try testing.expect(try a.toInt(i16) == -2048);
a.setSign(true);
try a.shiftRight(&arg7, 4);
- try testing.expect(try a.to(i16) == -2048);
+ try testing.expect(try a.toInt(i16) == -2048);
}
test "sat shift-left simple unsigned" {
@@ -2099,7 +2099,7 @@ test "sat shift-left simple unsigned" {
defer a.deinit();
try a.shiftLeftSat(&a, 16, .unsigned, 21);
- try testing.expect((try a.to(u64)) == 0x1fffff);
+ try testing.expect((try a.toInt(u64)) == 0x1fffff);
}
test "sat shift-left simple unsigned no sat" {
@@ -2107,7 +2107,7 @@ test "sat shift-left simple unsigned no sat" {
defer a.deinit();
try a.shiftLeftSat(&a, 16, .unsigned, 21);
- try testing.expect((try a.to(u64)) == 0x10000);
+ try testing.expect((try a.toInt(u64)) == 0x10000);
}
test "sat shift-left multi unsigned" {
@@ -2115,7 +2115,7 @@ test "sat shift-left multi unsigned" {
defer a.deinit();
try a.shiftLeftSat(&a, @bitSizeOf(DoubleLimb) - 3, .unsigned, @bitSizeOf(DoubleLimb) - 1);
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) >> 1);
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) >> 1);
}
test "sat shift-left unsigned shift > bitcount" {
@@ -2123,7 +2123,7 @@ test "sat shift-left unsigned shift > bitcount" {
defer a.deinit();
try a.shiftLeftSat(&a, 10, .unsigned, 10);
- try testing.expect((try a.to(u10)) == maxInt(u10));
+ try testing.expect((try a.toInt(u10)) == maxInt(u10));
}
test "sat shift-left unsigned zero" {
@@ -2131,7 +2131,7 @@ test "sat shift-left unsigned zero" {
defer a.deinit();
try a.shiftLeftSat(&a, 1, .unsigned, 0);
- try testing.expect((try a.to(u64)) == 0);
+ try testing.expect((try a.toInt(u64)) == 0);
}
test "sat shift-left unsigned negative" {
@@ -2139,7 +2139,7 @@ test "sat shift-left unsigned negative" {
defer a.deinit();
try a.shiftLeftSat(&a, 0, .unsigned, 0);
- try testing.expect((try a.to(u64)) == 0);
+ try testing.expect((try a.toInt(u64)) == 0);
}
test "sat shift-left signed simple negative" {
@@ -2147,7 +2147,7 @@ test "sat shift-left signed simple negative" {
defer a.deinit();
try a.shiftLeftSat(&a, 3, .signed, 10);
- try testing.expect((try a.to(i10)) == minInt(i10));
+ try testing.expect((try a.toInt(i10)) == minInt(i10));
}
test "sat shift-left signed simple positive" {
@@ -2155,7 +2155,7 @@ test "sat shift-left signed simple positive" {
defer a.deinit();
try a.shiftLeftSat(&a, 3, .signed, 10);
- try testing.expect((try a.to(i10)) == maxInt(i10));
+ try testing.expect((try a.toInt(i10)) == maxInt(i10));
}
test "sat shift-left signed multi positive" {
@@ -2170,7 +2170,7 @@ test "sat shift-left signed multi positive" {
defer a.deinit();
try a.shiftLeftSat(&a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == x <<| shift);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == x <<| shift);
}
test "sat shift-left signed multi negative" {
@@ -2185,7 +2185,7 @@ test "sat shift-left signed multi negative" {
defer a.deinit();
try a.shiftLeftSat(&a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == x <<| shift);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == x <<| shift);
}
test "bitNotWrap unsigned simple" {
@@ -2197,7 +2197,7 @@ test "bitNotWrap unsigned simple" {
try a.bitNotWrap(&a, .unsigned, 10);
- try testing.expect((try a.to(u10)) == ~x);
+ try testing.expect((try a.toInt(u10)) == ~x);
}
test "bitNotWrap unsigned multi" {
@@ -2206,7 +2206,7 @@ test "bitNotWrap unsigned multi" {
try a.bitNotWrap(&a, .unsigned, @bitSizeOf(DoubleLimb));
- try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb));
+ try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb));
}
test "bitNotWrap signed simple" {
@@ -2218,7 +2218,7 @@ test "bitNotWrap signed simple" {
try a.bitNotWrap(&a, .signed, 11);
- try testing.expect((try a.to(i11)) == ~x);
+ try testing.expect((try a.toInt(i11)) == ~x);
}
test "bitNotWrap signed multi" {
@@ -2227,7 +2227,7 @@ test "bitNotWrap signed multi" {
try a.bitNotWrap(&a, .signed, @bitSizeOf(SignedDoubleLimb));
- try testing.expect((try a.to(SignedDoubleLimb)) == -1);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -1);
}
test "bitNotWrap more than two limbs" {
@@ -2249,11 +2249,11 @@ test "bitNotWrap more than two limbs" {
try res.bitNotWrap(&a, .unsigned, bits);
const Unsigned = @Type(.{ .int = .{ .signedness = .unsigned, .bits = bits } });
- try testing.expectEqual((try res.to(Unsigned)), ~@as(Unsigned, maxInt(Limb)));
+ try testing.expectEqual((try res.toInt(Unsigned)), ~@as(Unsigned, maxInt(Limb)));
try res.bitNotWrap(&a, .signed, bits);
const Signed = @Type(.{ .int = .{ .signedness = .signed, .bits = bits } });
- try testing.expectEqual((try res.to(Signed)), ~@as(Signed, maxInt(Limb)));
+ try testing.expectEqual((try res.toInt(Signed)), ~@as(Signed, maxInt(Limb)));
}
test "bitwise and simple" {
@@ -2264,7 +2264,7 @@ test "bitwise and simple" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(u64)) == 0xeeeeeeee00000000);
+ try testing.expect((try a.toInt(u64)) == 0xeeeeeeee00000000);
}
test "bitwise and multi-limb" {
@@ -2275,7 +2275,7 @@ test "bitwise and multi-limb" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(u128)) == 0);
+ try testing.expect((try a.toInt(u128)) == 0);
}
test "bitwise and negative-positive simple" {
@@ -2286,7 +2286,7 @@ test "bitwise and negative-positive simple" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(u64)) == 0x22222222);
+ try testing.expect((try a.toInt(u64)) == 0x22222222);
}
test "bitwise and negative-positive multi-limb" {
@@ -2308,7 +2308,7 @@ test "bitwise and positive-negative simple" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(u64)) == 0x1111111111111110);
+ try testing.expect((try a.toInt(u64)) == 0x1111111111111110);
}
test "bitwise and positive-negative multi-limb" {
@@ -2330,7 +2330,7 @@ test "bitwise and negative-negative simple" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(i128)) == -0xffffffff33333332);
+ try testing.expect((try a.toInt(i128)) == -0xffffffff33333332);
}
test "bitwise and negative-negative multi-limb" {
@@ -2341,7 +2341,7 @@ test "bitwise and negative-negative multi-limb" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(i128)) == -maxInt(Limb) * 2 - 2);
+ try testing.expect((try a.toInt(i128)) == -maxInt(Limb) * 2 - 2);
}
test "bitwise and negative overflow" {
@@ -2352,7 +2352,7 @@ test "bitwise and negative overflow" {
try a.bitAnd(&a, &b);
- try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb) - 1);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb) - 1);
}
test "bitwise xor simple" {
@@ -2363,7 +2363,7 @@ test "bitwise xor simple" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(u64)) == 0x1111111133333333);
+ try testing.expect((try a.toInt(u64)) == 0x1111111133333333);
}
test "bitwise xor multi-limb" {
@@ -2378,7 +2378,7 @@ test "bitwise xor multi-limb" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(DoubleLimb)) == x ^ y);
+ try testing.expect((try a.toInt(DoubleLimb)) == x ^ y);
}
test "bitwise xor single negative simple" {
@@ -2389,7 +2389,7 @@ test "bitwise xor single negative simple" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(i64)) == -0x2efed94fcb932ef9);
+ try testing.expect((try a.toInt(i64)) == -0x2efed94fcb932ef9);
}
test "bitwise xor single negative multi-limb" {
@@ -2400,7 +2400,7 @@ test "bitwise xor single negative multi-limb" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(i128)) == -0x6a50889abd8834a24db1f19650d3999a);
+ try testing.expect((try a.toInt(i128)) == -0x6a50889abd8834a24db1f19650d3999a);
}
test "bitwise xor single negative overflow" {
@@ -2411,7 +2411,7 @@ test "bitwise xor single negative overflow" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(SignedDoubleLimb)) == -(maxInt(Limb) + 1));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -(maxInt(Limb) + 1));
}
test "bitwise xor double negative simple" {
@@ -2422,7 +2422,7 @@ test "bitwise xor double negative simple" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(u64)) == 0xc39c47081a6eb759);
+ try testing.expect((try a.toInt(u64)) == 0xc39c47081a6eb759);
}
test "bitwise xor double negative multi-limb" {
@@ -2433,7 +2433,7 @@ test "bitwise xor double negative multi-limb" {
try a.bitXor(&a, &b);
- try testing.expect((try a.to(u128)) == 0xa3492ec28e62c410dff92bf0549bf771);
+ try testing.expect((try a.toInt(u128)) == 0xa3492ec28e62c410dff92bf0549bf771);
}
test "bitwise or simple" {
@@ -2444,7 +2444,7 @@ test "bitwise or simple" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(u64)) == 0xffffffff33333333);
+ try testing.expect((try a.toInt(u64)) == 0xffffffff33333333);
}
test "bitwise or multi-limb" {
@@ -2455,7 +2455,7 @@ test "bitwise or multi-limb" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb));
+ try testing.expect((try a.toInt(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb));
}
test "bitwise or negative-positive simple" {
@@ -2466,7 +2466,7 @@ test "bitwise or negative-positive simple" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(i64)) == -0x1111111111111111);
+ try testing.expect((try a.toInt(i64)) == -0x1111111111111111);
}
test "bitwise or negative-positive multi-limb" {
@@ -2477,7 +2477,7 @@ test "bitwise or negative-positive multi-limb" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb));
}
test "bitwise or positive-negative simple" {
@@ -2488,7 +2488,7 @@ test "bitwise or positive-negative simple" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(i64)) == -0x22222221);
+ try testing.expect((try a.toInt(i64)) == -0x22222221);
}
test "bitwise or positive-negative multi-limb" {
@@ -2499,7 +2499,7 @@ test "bitwise or positive-negative multi-limb" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(SignedDoubleLimb)) == -1);
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -1);
}
test "bitwise or negative-negative simple" {
@@ -2510,7 +2510,7 @@ test "bitwise or negative-negative simple" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(i128)) == -0xeeeeeeee00000001);
+ try testing.expect((try a.toInt(i128)) == -0xeeeeeeee00000001);
}
test "bitwise or negative-negative multi-limb" {
@@ -2521,7 +2521,7 @@ test "bitwise or negative-negative multi-limb" {
try a.bitOr(&a, &b);
- try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb));
+ try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb));
}
test "var args" {
@@ -2531,7 +2531,7 @@ test "var args" {
var b = try Managed.initSet(testing.allocator, 6);
defer b.deinit();
try a.add(&a, &b);
- try testing.expect((try a.to(u64)) == 11);
+ try testing.expect((try a.toInt(u64)) == 11);
var c = try Managed.initSet(testing.allocator, 11);
defer c.deinit();
@@ -2552,7 +2552,7 @@ test "gcd non-one small" {
try r.gcd(&a, &b);
- try testing.expect((try r.to(u32)) == 1);
+ try testing.expect((try r.toInt(u32)) == 1);
}
test "gcd non-one medium" {
@@ -2565,7 +2565,7 @@ test "gcd non-one medium" {
try r.gcd(&a, &b);
- try testing.expect((try r.to(u32)) == 38);
+ try testing.expect((try r.toInt(u32)) == 38);
}
test "gcd non-one large" {
@@ -2578,7 +2578,7 @@ test "gcd non-one large" {
try r.gcd(&a, &b);
- try testing.expect((try r.to(u32)) == 4369);
+ try testing.expect((try r.toInt(u32)) == 4369);
}
test "gcd large multi-limb result" {
@@ -2593,7 +2593,7 @@ test "gcd large multi-limb result" {
try r.gcd(&a, &b);
- const answer = (try r.to(u256));
+ const answer = (try r.toInt(u256));
try testing.expect(answer == 0xf000000ff00000fff0000ffff000fffff00ffffff1);
}
@@ -2607,7 +2607,7 @@ test "gcd one large" {
try r.gcd(&a, &b);
- try testing.expect((try r.to(u64)) == 1);
+ try testing.expect((try r.toInt(u64)) == 1);
}
test "mutable to managed" {
@@ -2637,10 +2637,10 @@ test "pow" {
defer a.deinit();
try a.pow(&a, 3);
- try testing.expectEqual(@as(i32, -27), try a.to(i32));
+ try testing.expectEqual(@as(i32, -27), try a.toInt(i32));
try a.pow(&a, 4);
- try testing.expectEqual(@as(i32, 531441), try a.to(i32));
+ try testing.expectEqual(@as(i32, 531441), try a.toInt(i32));
}
{
var a = try Managed.initSet(testing.allocator, 10);
@@ -2671,18 +2671,18 @@ test "pow" {
defer a.deinit();
try a.pow(&a, 100);
- try testing.expectEqual(@as(i32, 0), try a.to(i32));
+ try testing.expectEqual(@as(i32, 0), try a.toInt(i32));
try a.set(1);
try a.pow(&a, 0);
- try testing.expectEqual(@as(i32, 1), try a.to(i32));
+ try testing.expectEqual(@as(i32, 1), try a.toInt(i32));
try a.pow(&a, 100);
- try testing.expectEqual(@as(i32, 1), try a.to(i32));
+ try testing.expectEqual(@as(i32, 1), try a.toInt(i32));
try a.set(-1);
try a.pow(&a, 15);
- try testing.expectEqual(@as(i32, -1), try a.to(i32));
+ try testing.expectEqual(@as(i32, -1), try a.toInt(i32));
try a.pow(&a, 16);
- try testing.expectEqual(@as(i32, 1), try a.to(i32));
+ try testing.expectEqual(@as(i32, 1), try a.toInt(i32));
}
}
@@ -2696,24 +2696,24 @@ test "sqrt" {
try r.set(0);
try a.set(25);
try r.sqrt(&a);
- try testing.expectEqual(@as(i32, 5), try r.to(i32));
+ try testing.expectEqual(@as(i32, 5), try r.toInt(i32));
// aliased
try a.set(25);
try a.sqrt(&a);
- try testing.expectEqual(@as(i32, 5), try a.to(i32));
+ try testing.expectEqual(@as(i32, 5), try a.toInt(i32));
// bottom
try r.set(0);
try a.set(24);
try r.sqrt(&a);
- try testing.expectEqual(@as(i32, 4), try r.to(i32));
+ try testing.expectEqual(@as(i32, 4), try r.toInt(i32));
// large number
try r.set(0);
try a.set(0x1_0000_0000_0000);
try r.sqrt(&a);
- try testing.expectEqual(@as(i32, 0x100_0000), try r.to(i32));
+ try testing.expectEqual(@as(i32, 0x100_0000), try r.toInt(i32));
}
test "regression test for 1 limb overflow with alias" {
@@ -3225,7 +3225,7 @@ test "Managed sqrt(0) = 0" {
try a.setString(10, "0");
try res.sqrt(&a);
- try testing.expectEqual(@as(i32, 0), try res.to(i32));
+ try testing.expectEqual(@as(i32, 0), try res.toInt(i32));
}
test "Managed sqrt(-1) = error" {
diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig
index ce93f40a25..08a2c23388 100644
--- a/lib/std/math/big/rational.zig
+++ b/lib/std/math/big/rational.zig
@@ -518,28 +518,28 @@ test "set" {
defer a.deinit();
try a.setInt(5);
- try testing.expect((try a.p.to(u32)) == 5);
- try testing.expect((try a.q.to(u32)) == 1);
+ try testing.expect((try a.p.toInt(u32)) == 5);
+ try testing.expect((try a.q.toInt(u32)) == 1);
try a.setRatio(7, 3);
- try testing.expect((try a.p.to(u32)) == 7);
- try testing.expect((try a.q.to(u32)) == 3);
+ try testing.expect((try a.p.toInt(u32)) == 7);
+ try testing.expect((try a.q.toInt(u32)) == 3);
try a.setRatio(9, 3);
- try testing.expect((try a.p.to(i32)) == 3);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == 3);
+ try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, 3);
- try testing.expect((try a.p.to(i32)) == -3);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == -3);
+ try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(9, -3);
- try testing.expect((try a.p.to(i32)) == -3);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == -3);
+ try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, -3);
- try testing.expect((try a.p.to(i32)) == 3);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == 3);
+ try testing.expect((try a.q.toInt(i32)) == 1);
}
test "setFloat" {
@@ -547,24 +547,24 @@ test "setFloat" {
defer a.deinit();
try a.setFloat(f64, 2.5);
- try testing.expect((try a.p.to(i32)) == 5);
- try testing.expect((try a.q.to(i32)) == 2);
+ try testing.expect((try a.p.toInt(i32)) == 5);
+ try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, -2.5);
- try testing.expect((try a.p.to(i32)) == -5);
- try testing.expect((try a.q.to(i32)) == 2);
+ try testing.expect((try a.p.toInt(i32)) == -5);
+ try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, 3.141593);
// = 3.14159297943115234375
- try testing.expect((try a.p.to(u32)) == 3294199);
- try testing.expect((try a.q.to(u32)) == 1048576);
+ try testing.expect((try a.p.toInt(u32)) == 3294199);
+ try testing.expect((try a.q.toInt(u32)) == 1048576);
try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
// = 72.1415931207124145885245525278151035308837890625
- try testing.expect((try a.p.to(u128)) == 5076513310880537);
- try testing.expect((try a.q.to(u128)) == 70368744177664);
+ try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
+ try testing.expect((try a.q.toInt(u128)) == 70368744177664);
}
test "setFloatString" {
@@ -574,8 +574,8 @@ test "setFloatString" {
try a.setFloatString("72.14159312071241458852455252781510353");
// = 72.1415931207124145885245525278151035308837890625
- try testing.expect((try a.p.to(u128)) == 7214159312071241458852455252781510353);
- try testing.expect((try a.q.to(u128)) == 100000000000000000000000000000000000);
+ try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
+ try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
}
test "toFloat" {
@@ -612,8 +612,8 @@ test "copy" {
defer b.deinit();
try a.copyInt(b);
- try testing.expect((try a.p.to(u32)) == 5);
- try testing.expect((try a.q.to(u32)) == 1);
+ try testing.expect((try a.p.toInt(u32)) == 5);
+ try testing.expect((try a.q.toInt(u32)) == 1);
var c = try Int.initSet(testing.allocator, 7);
defer c.deinit();
@@ -621,8 +621,8 @@ test "copy" {
defer d.deinit();
try a.copyRatio(c, d);
- try testing.expect((try a.p.to(u32)) == 7);
- try testing.expect((try a.q.to(u32)) == 3);
+ try testing.expect((try a.p.toInt(u32)) == 7);
+ try testing.expect((try a.q.toInt(u32)) == 3);
var e = try Int.initSet(testing.allocator, 9);
defer e.deinit();
@@ -630,8 +630,8 @@ test "copy" {
defer f.deinit();
try a.copyRatio(e, f);
- try testing.expect((try a.p.to(u32)) == 3);
- try testing.expect((try a.q.to(u32)) == 1);
+ try testing.expect((try a.p.toInt(u32)) == 3);
+ try testing.expect((try a.q.toInt(u32)) == 1);
}
test "negate" {
@@ -639,16 +639,16 @@ test "negate" {
defer a.deinit();
try a.setInt(-50);
- try testing.expect((try a.p.to(i32)) == -50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == -50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
- try testing.expect((try a.p.to(i32)) == 50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == 50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
- try testing.expect((try a.p.to(i32)) == -50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == -50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
}
test "abs" {
@@ -656,16 +656,16 @@ test "abs" {
defer a.deinit();
try a.setInt(-50);
- try testing.expect((try a.p.to(i32)) == -50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == -50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
- try testing.expect((try a.p.to(i32)) == 50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == 50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
- try testing.expect((try a.p.to(i32)) == 50);
- try testing.expect((try a.q.to(i32)) == 1);
+ try testing.expect((try a.p.toInt(i32)) == 50);
+ try testing.expect((try a.q.toInt(i32)) == 1);
}
test "swap" {
@@ -677,19 +677,19 @@ test "swap" {
try a.setRatio(50, 23);
try b.setRatio(17, 3);
- try testing.expect((try a.p.to(u32)) == 50);
- try testing.expect((try a.q.to(u32)) == 23);
+ try testing.expect((try a.p.toInt(u32)) == 50);
+ try testing.expect((try a.q.toInt(u32)) == 23);
- try testing.expect((try b.p.to(u32)) == 17);
- try testing.expect((try b.q.to(u32)) == 3);
+ try testing.expect((try b.p.toInt(u32)) == 17);
+ try testing.expect((try b.q.toInt(u32)) == 3);
a.swap(&b);
- try testing.expect((try a.p.to(u32)) == 17);
- try testing.expect((try a.q.to(u32)) == 3);
+ try testing.expect((try a.p.toInt(u32)) == 17);
+ try testing.expect((try a.q.toInt(u32)) == 3);
- try testing.expect((try b.p.to(u32)) == 50);
- try testing.expect((try b.q.to(u32)) == 23);
+ try testing.expect((try b.p.toInt(u32)) == 50);
+ try testing.expect((try b.q.toInt(u32)) == 23);
}
test "order" {
diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig
index 7cf201d4b1..bc3ef028b9 100644
--- a/lib/std/mem/Allocator.zig
+++ b/lib/std/mem/Allocator.zig
@@ -10,7 +10,10 @@ const builtin = @import("builtin");
pub const Error = error{OutOfMemory};
pub const Log2Align = math.Log2Int(usize);
-// The type erased pointer to the allocator implementation
+/// The type erased pointer to the allocator implementation.
+/// Any comparison of this field may result in illegal behavior, since it may be set to
+/// `undefined` in cases where the allocator implementation does not have any associated
+/// state.
ptr: *anyopaque,
vtable: *const VTable,
diff --git a/lib/std/meta.zig b/lib/std/meta.zig
index f218c916e8..c4e3774da5 100644
--- a/lib/std/meta.zig
+++ b/lib/std/meta.zig
@@ -418,12 +418,9 @@ test fieldInfo {
try testing.expect(comptime uf.type == u8);
}
+/// Deprecated: use @FieldType
pub fn FieldType(comptime T: type, comptime field: FieldEnum(T)) type {
- if (@typeInfo(T) != .@"struct" and @typeInfo(T) != .@"union") {
- @compileError("Expected struct or union, found '" ++ @typeName(T) ++ "'");
- }
-
- return fieldInfo(T, field).type;
+ return @FieldType(T, @tagName(field));
}
test FieldType {
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
index c030bd5208..365fb9f05f 100644
--- a/lib/std/os/linux.zig
+++ b/lib/std/os/linux.zig
@@ -608,11 +608,11 @@ pub inline fn vfork() usize {
return @call(.always_inline, syscall0, .{.vfork});
}
-pub fn futimens(fd: i32, times: *const [2]timespec) usize {
+pub fn futimens(fd: i32, times: ?*const [2]timespec) usize {
return utimensat(fd, null, times, 0);
}
-pub fn utimensat(dirfd: i32, path: ?[*:0]const u8, times: *const [2]timespec, flags: u32) usize {
+pub fn utimensat(dirfd: i32, path: ?[*:0]const u8, times: ?*const [2]timespec, flags: u32) usize {
return syscall4(.utimensat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(times), flags);
}
@@ -854,7 +854,7 @@ pub fn readlinkat(dirfd: i32, noalias path: [*:0]const u8, noalias buf_ptr: [*]u
return syscall4(.readlinkat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(buf_ptr), buf_len);
}
-pub fn mkdir(path: [*:0]const u8, mode: u32) usize {
+pub fn mkdir(path: [*:0]const u8, mode: mode_t) usize {
if (@hasField(SYS, "mkdir")) {
return syscall2(.mkdir, @intFromPtr(path), mode);
} else {
@@ -862,7 +862,7 @@ pub fn mkdir(path: [*:0]const u8, mode: u32) usize {
}
}
-pub fn mkdirat(dirfd: i32, path: [*:0]const u8, mode: u32) usize {
+pub fn mkdirat(dirfd: i32, path: [*:0]const u8, mode: mode_t) usize {
return syscall3(.mkdirat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode);
}
@@ -1878,6 +1878,17 @@ pub fn recvmsg(fd: i32, msg: *msghdr, flags: u32) usize {
}
}
+pub fn recvmmsg(fd: i32, msgvec: ?[*]mmsghdr, vlen: u32, flags: u32, timeout: ?*timespec) usize {
+ return syscall5(
+ .recvmmsg,
+ @as(usize, @bitCast(@as(isize, fd))),
+ @intFromPtr(msgvec),
+ vlen,
+ flags,
+ @intFromPtr(timeout),
+ );
+}
+
pub fn recvfrom(
fd: i32,
noalias buf: [*]u8,
@@ -2204,7 +2215,7 @@ pub fn eventfd(count: u32, flags: u32) usize {
return syscall2(.eventfd2, count, flags);
}
-pub fn timerfd_create(clockid: clockid_t, flags: TFD) usize {
+pub fn timerfd_create(clockid: timerfd_clockid_t, flags: TFD) usize {
return syscall2(
.timerfd_create,
@intFromEnum(clockid),
@@ -4685,8 +4696,35 @@ pub const clockid_t = enum(u32) {
BOOTTIME = 7,
REALTIME_ALARM = 8,
BOOTTIME_ALARM = 9,
- SGI_CYCLE = 10,
- TAI = 11,
+ // In the linux kernel header file (time.h) is the following note:
+ // * The driver implementing this got removed. The clock ID is kept as a
+ // * place holder. Do not reuse!
+ // Therefore, calling clock_gettime() with these IDs will result in an error.
+ //
+ // Some backgrond:
+ // - SGI_CYCLE was for Silicon Graphics (SGI) workstations,
+ // which are probably no longer in use, so it makes sense to disable
+ // - TAI_CLOCK was designed as CLOCK_REALTIME(UTC) + tai_offset,
+ // but tai_offset was always 0 in the kernel.
+ // So there is no point in using this clock.
+ // SGI_CYCLE = 10,
+ // TAI = 11,
+ _,
+};
+
+// For use with posix.timerfd_create()
+// Actually, the parameter for the timerfd_create() function is in integer,
+// which means that the developer has to figure out which value is appropriate.
+// To make this easier and, above all, safer, because an incorrect value leads
+// to a panic, an enum is introduced which only allows the values
+// that actually work.
+pub const TIMERFD_CLOCK = timerfd_clockid_t;
+pub const timerfd_clockid_t = enum(u32) {
+ REALTIME = 0,
+ MONOTONIC = 1,
+ BOOTTIME = 7,
+ REALTIME_ALARM = 8,
+ BOOTTIME_ALARM = 9,
_,
};
diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig
index 0bc982140b..dcde986887 100644
--- a/lib/std/os/linux/test.zig
+++ b/lib/std/os/linux/test.zig
@@ -41,7 +41,7 @@ test "timer" {
var err: linux.E = linux.E.init(epoll_fd);
try expect(err == .SUCCESS);
- const timer_fd = linux.timerfd_create(linux.CLOCK.MONOTONIC, .{});
+ const timer_fd = linux.timerfd_create(linux.TIMERFD_CLOCK.MONOTONIC, .{});
try expect(linux.E.init(timer_fd) == .SUCCESS);
const time_interval = linux.timespec{
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index e0e9b83771..d3db40bac2 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -1061,7 +1061,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
// us INVALID_PARAMETER.
// The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5).
var need_fallback = true;
- if (builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {
+ if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {
// Deletion with posix semantics if the filesystem supports it.
var info = FILE_DISPOSITION_INFORMATION_EX{
.Flags = FILE_DISPOSITION_DELETE |
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
index 94d63cf9ef..c9e67f7d38 100644
--- a/lib/std/posix.zig
+++ b/lib/std/posix.zig
@@ -121,6 +121,7 @@ pub const blkcnt_t = system.blkcnt_t;
pub const blksize_t = system.blksize_t;
pub const clock_t = system.clock_t;
pub const clockid_t = system.clockid_t;
+pub const timerfd_clockid_t = system.timerfd_clockid_t;
pub const cpu_set_t = system.cpu_set_t;
pub const dev_t = system.dev_t;
pub const dl_phdr_info = system.dl_phdr_info;
@@ -155,6 +156,7 @@ pub const socklen_t = system.socklen_t;
pub const stack_t = system.stack_t;
pub const time_t = system.time_t;
pub const timespec = system.timespec;
+pub const timestamp_t = system.timestamp_t;
pub const timeval = system.timeval;
pub const timezone = system.timezone;
pub const ucontext_t = system.ucontext_t;
@@ -2875,7 +2877,7 @@ pub fn renameatW(
/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
-pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
+pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
if (native_os == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
@@ -2887,7 +2889,7 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
}
}
-pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
+pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
_ = mode;
switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
.SUCCESS => return,
@@ -2912,7 +2914,7 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr
}
/// Same as `mkdirat` except the parameters are null-terminated.
-pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
+pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
if (native_os == .windows) {
const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
@@ -2946,7 +2948,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
}
/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
-pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
+pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: mode_t) MakeDirError!void {
_ = mode;
const sub_dir_handle = windows.OpenFile(sub_path_w, .{
.dir = dir_fd,
@@ -2994,7 +2996,7 @@ pub const MakeDirError = error{
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
-pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
+pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void {
if (native_os == .wasi and !builtin.link_libc) {
return mkdirat(wasi.AT.FDCWD, dir_path, mode);
} else if (native_os == .windows) {
@@ -3010,7 +3012,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
/// On WASI, `dir_path` should be encoded as valid UTF-8.
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
-pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
+pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
if (native_os == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
return mkdirW(dir_path_w.span(), mode);
@@ -3041,7 +3043,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
}
/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
-pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
+pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
_ = mode;
const sub_dir_handle = windows.OpenFile(dir_path_w, .{
.dir = fs.cwd().fd,
@@ -4276,6 +4278,35 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
}
}
+pub const GetSockOptError = error{
+ /// The calling process does not have the appropriate privileges.
+ AccessDenied,
+
+ /// The option is not supported by the protocol.
+ InvalidProtocolOption,
+
+ /// Insufficient resources are available in the system to complete the call.
+ SystemResources,
+} || UnexpectedError;
+
+pub fn getsockopt(fd: socket_t, level: i32, optname: u32, opt: []u8) GetSockOptError!void {
+ var len: socklen_t = undefined;
+ switch (errno(system.getsockopt(fd, level, optname, opt.ptr, &len))) {
+ .SUCCESS => {
+ std.debug.assert(len == opt.len);
+ },
+ .BADF => unreachable,
+ .NOTSOCK => unreachable,
+ .INVAL => unreachable,
+ .FAULT => unreachable,
+ .NOPROTOOPT => return error.InvalidProtocolOption,
+ .NOMEM => return error.SystemResources,
+ .NOBUFS => return error.SystemResources,
+ .ACCES => return error.AccessDenied,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
var err_code: i32 = undefined;
var size: u32 = @sizeOf(u32);
@@ -5624,13 +5655,13 @@ pub fn dl_iterate_phdr(
pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
-/// TODO: change this to return the timespec as a return value
-pub fn clock_gettime(clock_id: clockid_t, tp: *timespec) ClockGetTimeError!void {
+pub fn clock_gettime(clock_id: clockid_t) ClockGetTimeError!timespec {
+ var tp: timespec = undefined;
if (native_os == .wasi and !builtin.link_libc) {
- var ts: wasi.timestamp_t = undefined;
+ var ts: timestamp_t = undefined;
switch (system.clock_time_get(clock_id, 1, &ts)) {
.SUCCESS => {
- tp.* = .{
+ tp = .{
.sec = @intCast(ts / std.time.ns_per_s),
.nsec = @intCast(ts % std.time.ns_per_s),
};
@@ -5638,7 +5669,7 @@ pub fn clock_gettime(clock_id: clockid_t, tp: *timespec) ClockGetTimeError!void
.INVAL => return error.UnsupportedClock,
else => |err| return unexpectedErrno(err),
}
- return;
+ return tp;
}
if (native_os == .windows) {
if (clock_id == .REALTIME) {
@@ -5647,19 +5678,19 @@ pub fn clock_gettime(clock_id: clockid_t, tp: *timespec) ClockGetTimeError!void
// FileTime has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch.
const ft64 = (@as(u64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
const ft_per_s = std.time.ns_per_s / 100;
- tp.* = .{
+ tp = .{
.sec = @as(i64, @intCast(ft64 / ft_per_s)) + std.time.epoch.windows,
.nsec = @as(c_long, @intCast(ft64 % ft_per_s)) * 100,
};
- return;
+ return tp;
} else {
// TODO POSIX implementation of CLOCK.MONOTONIC on Windows.
return error.UnsupportedClock;
}
}
- switch (errno(system.clock_gettime(clock_id, tp))) {
- .SUCCESS => return,
+ switch (errno(system.clock_gettime(clock_id, &tp))) {
+ .SUCCESS => return tp,
.FAULT => unreachable,
.INVAL => return error.UnsupportedClock,
else => |err| return unexpectedErrno(err),
@@ -5668,7 +5699,7 @@ pub fn clock_gettime(clock_id: clockid_t, tp: *timespec) ClockGetTimeError!void
pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockGetTimeError!void {
if (native_os == .wasi and !builtin.link_libc) {
- var ts: wasi.timestamp_t = undefined;
+ var ts: timestamp_t = undefined;
switch (system.clock_res_get(@bitCast(clock_id), &ts)) {
.SUCCESS => res.* = .{
.sec = @intCast(ts / std.time.ns_per_s),
@@ -5767,17 +5798,27 @@ pub const FutimensError = error{
ReadOnlyFileSystem,
} || UnexpectedError;
-pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
+pub fn futimens(fd: fd_t, times: ?*const [2]timespec) FutimensError!void {
if (native_os == .wasi and !builtin.link_libc) {
// TODO WASI encodes `wasi.fstflags` to signify magic values
// similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore
// this here, but we should really handle it somehow.
- const atim = times[0].toTimestamp();
- const mtim = times[1].toTimestamp();
- switch (wasi.fd_filestat_set_times(fd, atim, mtim, .{
- .ATIM = true,
- .MTIM = true,
- })) {
+ const error_code = blk: {
+ if (times) |times_arr| {
+ const atim = times_arr[0].toTimestamp();
+ const mtim = times_arr[1].toTimestamp();
+ break :blk wasi.fd_filestat_set_times(fd, atim, mtim, .{
+ .ATIM = true,
+ .MTIM = true,
+ });
+ }
+
+ break :blk wasi.fd_filestat_set_times(fd, 0, 0, .{
+ .ATIM_NOW = true,
+ .MTIM_NOW = true,
+ });
+ };
+ switch (error_code) {
.SUCCESS => return,
.ACCES => return error.AccessDenied,
.PERM => return error.PermissionDenied,
@@ -5871,8 +5912,7 @@ pub fn res_mkquery(
q[i + 3] = class;
// Make a reasonably unpredictable id
- var ts: timespec = undefined;
- clock_gettime(.REALTIME, &ts) catch {};
+ const ts = clock_gettime(.REALTIME) catch unreachable;
const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.nsec)));
const unsec: UInt = @bitCast(ts.nsec);
const id: u32 = @truncate(unsec + unsec / 65536);
@@ -6821,7 +6861,7 @@ pub fn memfd_createZ(name: [*:0]const u8, flags: u32) MemFdCreateError!fd_t {
}
},
.freebsd => {
- if (builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
+ if (comptime builtin.os.version_range.semver.max.order(.{ .major = 13, .minor = 0, .patch = 0 }) == .lt)
@compileError("memfd_create is unavailable on FreeBSD < 13.0");
const rc = system.memfd_create(name, flags);
switch (errno(rc)) {
@@ -7254,7 +7294,7 @@ pub const TimerFdCreateError = error{
pub const TimerFdGetError = error{InvalidHandle} || UnexpectedError;
pub const TimerFdSetError = TimerFdGetError || error{Canceled};
-pub fn timerfd_create(clock_id: clockid_t, flags: system.TFD) TimerFdCreateError!fd_t {
+pub fn timerfd_create(clock_id: system.timerfd_clockid_t, flags: system.TFD) TimerFdCreateError!fd_t {
const rc = system.timerfd_create(clock_id, @bitCast(flags));
return switch (errno(rc)) {
.SUCCESS => @intCast(rc),
diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig
index a5ea649c46..c7b315afef 100644
--- a/lib/std/priority_queue.zig
+++ b/lib/std/priority_queue.zig
@@ -182,8 +182,14 @@ pub fn PriorityQueue(comptime T: type, comptime Context: type, comptime compareF
better_capacity += better_capacity / 2 + 8;
if (better_capacity >= new_capacity) break;
}
+ try self.ensureTotalCapacityPrecise(better_capacity);
+ }
+
+ pub fn ensureTotalCapacityPrecise(self: *Self, new_capacity: usize) !void {
+ if (self.capacity() >= new_capacity) return;
+
const old_memory = self.allocatedSlice();
- const new_memory = try self.allocator.realloc(old_memory, better_capacity);
+ const new_memory = try self.allocator.realloc(old_memory, new_capacity);
self.items.ptr = new_memory.ptr;
self.cap = new_memory.len;
}
@@ -211,6 +217,16 @@ pub fn PriorityQueue(comptime T: type, comptime Context: type, comptime compareF
self.cap = new_memory.len;
}
+ pub fn clearRetainingCapacity(self: *Self) void {
+ self.items.len = 0;
+ }
+
+ pub fn clearAndFree(self: *Self) void {
+ self.allocator.free(self.allocatedSlice());
+ self.items.len = 0;
+ self.cap = 0;
+ }
+
pub fn update(self: *Self, elem: T, new_elem: T) !void {
const update_index = blk: {
var idx: usize = 0;
diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig
index fa824e0aec..8a4cab5e83 100644
--- a/lib/std/process/Child.zig
+++ b/lib/std/process/Child.zig
@@ -344,15 +344,17 @@ pub const RunResult = struct {
stderr: []u8,
};
-fn fifoToOwnedArrayList(fifo: *std.io.PollFifo) std.ArrayList(u8) {
+fn writeFifoDataToArrayList(allocator: Allocator, list: *std.ArrayListUnmanaged(u8), fifo: *std.io.PollFifo) !void {
if (fifo.head != 0) fifo.realign();
- const result = std.ArrayList(u8){
- .items = fifo.buf[0..fifo.count],
- .capacity = fifo.buf.len,
- .allocator = fifo.allocator,
- };
- fifo.* = std.io.PollFifo.init(fifo.allocator);
- return result;
+ if (list.capacity == 0) {
+ list.* = .{
+ .items = fifo.buf[0..fifo.count],
+ .capacity = fifo.buf.len,
+ };
+ fifo.* = std.io.PollFifo.init(fifo.allocator);
+ } else {
+ try list.appendSlice(allocator, fifo.buf[0..fifo.count]);
+ }
}
/// Collect the output from the process's stdout and stderr. Will return once all output
@@ -362,21 +364,16 @@ fn fifoToOwnedArrayList(fifo: *std.io.PollFifo) std.ArrayList(u8) {
/// The process must be started with stdout_behavior and stderr_behavior == .Pipe
pub fn collectOutput(
child: ChildProcess,
- stdout: *std.ArrayList(u8),
- stderr: *std.ArrayList(u8),
+ /// Used for `stdout` and `stderr`.
+ allocator: Allocator,
+ stdout: *std.ArrayListUnmanaged(u8),
+ stderr: *std.ArrayListUnmanaged(u8),
max_output_bytes: usize,
) !void {
assert(child.stdout_behavior == .Pipe);
assert(child.stderr_behavior == .Pipe);
- // we could make this work with multiple allocators but YAGNI
- if (stdout.allocator.ptr != stderr.allocator.ptr or
- stdout.allocator.vtable != stderr.allocator.vtable)
- {
- unreachable; // ChildProcess.collectOutput only supports 1 allocator
- }
-
- var poller = std.io.poll(stdout.allocator, enum { stdout, stderr }, .{
+ var poller = std.io.poll(allocator, enum { stdout, stderr }, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
});
@@ -389,8 +386,8 @@ pub fn collectOutput(
return error.StderrStreamTooLong;
}
- stdout.* = fifoToOwnedArrayList(poller.fifo(.stdout));
- stderr.* = fifoToOwnedArrayList(poller.fifo(.stderr));
+ try writeFifoDataToArrayList(allocator, stdout, poller.fifo(.stdout));
+ try writeFifoDataToArrayList(allocator, stderr, poller.fifo(.stderr));
}
pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
@@ -420,20 +417,21 @@ pub fn run(args: struct {
child.expand_arg0 = args.expand_arg0;
child.progress_node = args.progress_node;
- var stdout = std.ArrayList(u8).init(args.allocator);
- var stderr = std.ArrayList(u8).init(args.allocator);
- errdefer {
- stdout.deinit();
- stderr.deinit();
- }
+ var stdout: std.ArrayListUnmanaged(u8) = .empty;
+ errdefer stdout.deinit(args.allocator);
+ var stderr: std.ArrayListUnmanaged(u8) = .empty;
+ errdefer stderr.deinit(args.allocator);
try child.spawn();
- try child.collectOutput(&stdout, &stderr, args.max_output_bytes);
+ errdefer {
+ _ = child.kill() catch {};
+ }
+ try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes);
return RunResult{
+ .stdout = try stdout.toOwnedSlice(args.allocator),
+ .stderr = try stderr.toOwnedSlice(args.allocator),
.term = try child.wait(),
- .stdout = try stdout.toOwnedSlice(),
- .stderr = try stderr.toOwnedSlice(),
};
}
diff --git a/lib/std/std.zig b/lib/std/std.zig
index cc61111746..5c997aebaf 100644
--- a/lib/std/std.zig
+++ b/lib/std/std.zig
@@ -93,6 +93,7 @@ pub const valgrind = @import("valgrind.zig");
pub const wasm = @import("wasm.zig");
pub const zig = @import("zig.zig");
pub const zip = @import("zip.zig");
+pub const zon = @import("zon.zig");
pub const start = @import("start.zig");
const root = @import("root");
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
index 4d334b3407..1c109c48e2 100644
--- a/lib/std/testing.zig
+++ b/lib/std/testing.zig
@@ -125,7 +125,7 @@ fn expectEqualInner(comptime T: type, expected: T, actual: T) !void {
var i: usize = 0;
while (i < info.len) : (i += 1) {
if (!std.meta.eql(expected[i], actual[i])) {
- print("index {} incorrect. expected {}, found {}\n", .{
+ print("index {d} incorrect. expected {any}, found {any}\n", .{
i, expected[i], actual[i],
});
return error.TestExpectedEqual;
@@ -214,6 +214,34 @@ test "expectEqual union with comptime-only field" {
try expectEqual(U{ .a = {} }, .a);
}
+test "expectEqual nested array" {
+ const a = [2][2]f32{
+ [_]f32{ 1.0, 0.0 },
+ [_]f32{ 0.0, 1.0 },
+ };
+
+ const b = [2][2]f32{
+ [_]f32{ 1.0, 0.0 },
+ [_]f32{ 0.0, 1.0 },
+ };
+
+ try expectEqual(a, b);
+}
+
+test "expectEqual vector" {
+ const a: @Vector(4, u32) = @splat(4);
+ const b: @Vector(4, u32) = @splat(4);
+
+ try expectEqual(a, b);
+}
+
+test "expectEqual null" {
+ const a = .{null};
+ const b = @Vector(1, ?*u8){null};
+
+ try expectEqual(a, b);
+}
+
/// This function is intended to be used only in tests. When the formatted result of the template
/// and its arguments does not equal the expected text, it prints diagnostics to stderr to show how
/// they are not equal, then returns an error. It depends on `expectEqualStrings()` for printing
@@ -584,27 +612,6 @@ pub fn tmpDir(opts: std.fs.Dir.OpenOptions) TmpDir {
};
}
-test "expectEqual nested array" {
- const a = [2][2]f32{
- [_]f32{ 1.0, 0.0 },
- [_]f32{ 0.0, 1.0 },
- };
-
- const b = [2][2]f32{
- [_]f32{ 1.0, 0.0 },
- [_]f32{ 0.0, 1.0 },
- };
-
- try expectEqual(a, b);
-}
-
-test "expectEqual vector" {
- const a: @Vector(4, u32) = @splat(4);
- const b: @Vector(4, u32) = @splat(4);
-
- try expectEqual(a, b);
-}
-
pub fn expectEqualStrings(expected: []const u8, actual: []const u8) !void {
if (std.mem.indexOfDiff(u8, actual, expected)) |diff_index| {
print("\n====== expected this output: =========\n", .{});
diff --git a/lib/std/time.zig b/lib/std/time.zig
index d253b63512..3dbe2e837b 100644
--- a/lib/std/time.zig
+++ b/lib/std/time.zig
@@ -68,8 +68,7 @@ pub fn nanoTimestamp() i128 {
return value.toEpoch();
},
else => {
- var ts: posix.timespec = undefined;
- posix.clock_gettime(.REALTIME, &ts) catch |err| switch (err) {
+ const ts = posix.clock_gettime(.REALTIME) catch |err| switch (err) {
error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS".
};
return (@as(i128, ts.sec) * ns_per_s) + ts.nsec;
@@ -171,8 +170,7 @@ pub const Instant = struct {
else => posix.CLOCK.MONOTONIC,
};
- var ts: posix.timespec = undefined;
- posix.clock_gettime(clock_id, &ts) catch return error.Unsupported;
+ const ts = posix.clock_gettime(clock_id) catch return error.Unsupported;
return .{ .timestamp = ts };
}
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
index 292e069820..d2521be1ba 100644
--- a/lib/std/zig.zig
+++ b/lib/std/zig.zig
@@ -54,11 +54,8 @@ pub const Color = enum {
}
pub fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions {
- const ttyconf = get_tty_conf(color);
return .{
- .ttyconf = ttyconf,
- .include_source_line = ttyconf != .no_color,
- .include_reference_trace = ttyconf != .no_color,
+ .ttyconf = get_tty_conf(color),
};
}
};
@@ -749,7 +746,6 @@ pub const SimpleComptimeReason = enum(u32) {
atomic_order,
array_mul_factor,
slice_cat_operand,
- comptime_call_target,
inline_call_target,
generic_call_target,
wasm_memory_index,
@@ -830,7 +826,6 @@ pub const SimpleComptimeReason = enum(u32) {
.atomic_order => "atomic order must be comptime-known",
.array_mul_factor => "array multiplication factor must be comptime-known",
.slice_cat_operand => "slice being concatenated must be comptime-known",
- .comptime_call_target => "function being called at comptime must be comptime-known",
.inline_call_target => "function being called inline must be comptime-known",
.generic_call_target => "generic function being called must be comptime-known",
.wasm_memory_index => "wasm memory index must be comptime-known",
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 3f69ce5aeb..d4503b95ca 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -458,6 +458,19 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
return stream.writeAll("for input is not captured");
},
+ .invalid_byte => {
+ const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..];
+ return stream.print("{s} contains invalid byte: '{'}'", .{
+ switch (tok_slice[0]) {
+ '\'' => "character literal",
+ '"', '\\' => "string literal",
+ '/' => "comment",
+ else => unreachable,
+ },
+ std.zig.fmtEscapes(tok_slice[parse_error.extra.offset..][0..1]),
+ });
+ },
+
.expected_token => {
const found_tag = token_tags[parse_error.token + @intFromBool(parse_error.token_is_prev)];
const expected_symbol = parse_error.extra.expected_tag.symbol();
@@ -2926,6 +2939,7 @@ pub const Error = struct {
extra: union {
none: void,
expected_tag: Token.Tag,
+ offset: usize,
} = .{ .none = {} },
pub const Tag = enum {
@@ -2996,6 +3010,9 @@ pub const Error = struct {
/// `expected_tag` is populated.
expected_token,
+
+ /// `offset` is populated
+ invalid_byte,
};
};
diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig
index d6207d16a8..c105a371ef 100644
--- a/lib/std/zig/AstGen.zig
+++ b/lib/std/zig/AstGen.zig
@@ -851,7 +851,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
.async_call_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
- return callExpr(gz, scope, ri, node, tree.fullCall(&buf, node).?);
+ return callExpr(gz, scope, ri, .none, node, tree.fullCall(&buf, node).?);
},
.unreachable_literal => {
@@ -3009,8 +3009,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.validate_ptr_array_init,
.validate_ref_ty,
.validate_const,
- .try_operand_ty,
- .try_ref_operand_ty,
=> break :b true,
.@"defer" => unreachable,
@@ -6158,20 +6156,24 @@ fn tryExpr(
const try_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
const operand_rl: ResultInfo.Loc, const block_tag: Zir.Inst.Tag = switch (ri.rl) {
- .ref => .{ .ref, .try_ptr },
- .ref_coerced_ty => |payload_ptr_ty| .{
- .{ .ref_coerced_ty = try parent_gz.addUnNode(.try_ref_operand_ty, payload_ptr_ty, node) },
- .try_ptr,
- },
- else => if (try ri.rl.resultType(parent_gz, node)) |payload_ty| .{
- // `coerced_ty` is OK due to the `rvalue` call below
- .{ .coerced_ty = try parent_gz.addUnNode(.try_operand_ty, payload_ty, node) },
- .@"try",
- } else .{ .none, .@"try" },
+ .ref, .ref_coerced_ty => .{ .ref, .try_ptr },
+ else => .{ .none, .@"try" },
};
const operand_ri: ResultInfo = .{ .rl = operand_rl, .ctx = .error_handling_expr };
- // This could be a pointer or value depending on the `ri` parameter.
- const operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, node);
+ const operand = operand: {
+ // As a special case, we need to detect this form:
+ // `try .foo(...)`
+ // This is a decl literal form, even though we don't propagate a result type through `try`.
+ var buf: [1]Ast.Node.Index = undefined;
+ if (astgen.tree.fullCall(&buf, operand_node)) |full_call| {
+ const res_ty: Zir.Inst.Ref = try ri.rl.resultType(parent_gz, operand_node) orelse .none;
+ break :operand try callExpr(parent_gz, scope, operand_ri, res_ty, operand_node, full_call);
+ }
+
+ // This could be a pointer or value depending on the `ri` parameter.
+ break :operand try reachableExpr(parent_gz, scope, operand_ri, operand_node, node);
+ };
+
const try_inst = try parent_gz.makeBlockInst(block_tag, node);
try parent_gz.instructions.append(astgen.gpa, try_inst);
@@ -9446,7 +9448,18 @@ fn builtinCall(
} else if (str.len == 0) {
return astgen.failTok(str_lit_token, "import path cannot be empty", .{});
}
- const result = try gz.addStrTok(.import, str.index, str_lit_token);
+ const res_ty = try ri.rl.resultType(gz, node) orelse .none;
+ const payload_index = try addExtra(gz.astgen, Zir.Inst.Import{
+ .res_ty = res_ty,
+ .path = str.index,
+ });
+ const result = try gz.add(.{
+ .tag = .import,
+ .data = .{ .pl_tok = .{
+ .src_tok = gz.tokenIndexToRelative(str_lit_token),
+ .payload_index = payload_index,
+ } },
+ });
const gop = try astgen.imports.getOrPut(astgen.gpa, str.index);
if (!gop.found_existing) {
gop.value_ptr.* = str_lit_token;
@@ -10236,12 +10249,15 @@ fn callExpr(
gz: *GenZir,
scope: *Scope,
ri: ResultInfo,
+ /// If this is not `.none` and this call is a decl literal form (`.foo(...)`), then this
+ /// type is used as the decl literal result type instead of the result type from `ri.rl`.
+ override_decl_literal_type: Zir.Inst.Ref,
node: Ast.Node.Index,
call: Ast.full.Call,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
- const callee = try calleeExpr(gz, scope, ri.rl, call.ast.fn_expr);
+ const callee = try calleeExpr(gz, scope, ri.rl, override_decl_literal_type, call.ast.fn_expr);
const modifier: std.builtin.CallModifier = blk: {
if (call.async_token != null) {
break :blk .async_kw;
@@ -10367,6 +10383,9 @@ fn calleeExpr(
gz: *GenZir,
scope: *Scope,
call_rl: ResultInfo.Loc,
+ /// If this is not `.none` and this call is a decl literal form (`.foo(...)`), then this
+ /// type is used as the decl literal result type instead of the result type from `call_rl`.
+ override_decl_literal_type: Zir.Inst.Ref,
node: Ast.Node.Index,
) InnerError!Callee {
const astgen = gz.astgen;
@@ -10393,7 +10412,14 @@ fn calleeExpr(
.field_name_start = str_index,
} };
},
- .enum_literal => if (try call_rl.resultType(gz, node)) |res_ty| {
+ .enum_literal => {
+ const res_ty = res_ty: {
+ if (override_decl_literal_type != .none) break :res_ty override_decl_literal_type;
+ break :res_ty try call_rl.resultType(gz, node) orelse {
+ // No result type; lower to a literal call of an enum literal.
+ return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) };
+ };
+ };
// Decl literal call syntax, e.g.
// `const foo: T = .init();`
// Look up `init` in `T`, but don't try and coerce it.
@@ -10403,8 +10429,6 @@ fn calleeExpr(
.field_name_start = str_index,
});
return .{ .direct = callee };
- } else {
- return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) };
},
else => return .{ .direct = try expr(gz, scope, .{ .rl = .none }, node) },
}
@@ -11538,9 +11562,21 @@ fn parseStrLit(
}
}
-fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, bytes: []const u8, offset: u32) InnerError {
+fn failWithStrLitError(
+ astgen: *AstGen,
+ err: std.zig.string_literal.Error,
+ token: Ast.TokenIndex,
+ bytes: []const u8,
+ offset: u32,
+) InnerError {
const raw_string = bytes[offset..];
- return err.lower(raw_string, offset, AstGen.failOff, .{ astgen, token });
+ return failOff(
+ astgen,
+ token,
+ @intCast(offset + err.offset()),
+ "{}",
+ .{err.fmt(raw_string)},
+ );
}
fn failNode(
@@ -13981,6 +14017,39 @@ fn lowerAstErrors(astgen: *AstGen) !void {
var notes: std.ArrayListUnmanaged(u32) = .empty;
defer notes.deinit(gpa);
+ const token_starts = tree.tokens.items(.start);
+ const token_tags = tree.tokens.items(.tag);
+ const parse_err = tree.errors[0];
+ const tok = parse_err.token + @intFromBool(parse_err.token_is_prev);
+ const tok_start = token_starts[tok];
+ const start_char = tree.source[tok_start];
+
+ if (token_tags[tok] == .invalid and
+ (start_char == '\"' or start_char == '\'' or start_char == '/' or mem.startsWith(u8, tree.source[tok_start..], "\\\\")))
+ {
+ const tok_len: u32 = @intCast(tree.tokenSlice(tok).len);
+ const tok_end = tok_start + tok_len;
+ const bad_off = blk: {
+ var idx = tok_start;
+ while (idx < tok_end) : (idx += 1) {
+ switch (tree.source[idx]) {
+ 0x00...0x09, 0x0b...0x1f, 0x7f => break,
+ else => {},
+ }
+ }
+ break :blk idx - tok_start;
+ };
+
+ const err: Ast.Error = .{
+ .tag = Ast.Error.Tag.invalid_byte,
+ .token = tok,
+ .extra = .{ .offset = bad_off },
+ };
+ msg.clearRetainingCapacity();
+ try tree.renderError(err, msg.writer(gpa));
+ return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.items}, notes.items);
+ }
+
var cur_err = tree.errors[0];
for (tree.errors[1..]) |err| {
if (err.is_note) {
diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig
index f26bc288f4..092a354de6 100644
--- a/lib/std/zig/Zir.zig
+++ b/lib/std/zig/Zir.zig
@@ -483,7 +483,7 @@ pub const Inst = struct {
/// Uses the `pl_node` union field. `payload_index` points to a `FuncFancy`.
func_fancy,
/// Implements the `@import` builtin.
- /// Uses the `str_tok` field.
+ /// Uses the `pl_tok` field.
import,
/// Integer literal that fits in a u64. Uses the `int` union field.
int,
@@ -721,14 +721,6 @@ pub const Inst = struct {
/// Result is always void.
/// Uses the `un_node` union field. Node is the initializer. Operand is the initializer value.
validate_const,
- /// Given a type `T`, construct the type `E!T`, where `E` is this function's error set, to be used
- /// as the result type of a `try` operand. Generic poison is propagated.
- /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `T`.
- try_operand_ty,
- /// Given a type `*T`, construct the type `*E!T`, where `E` is this function's error set, to be used
- /// as the result type of a `try` operand whose address is taken with `&`. Generic poison is propagated.
- /// Uses the `un_node` union field. Node is the `try` expression. Operand is the type `*T`.
- try_ref_operand_ty,
// The following tags all relate to struct initialization expressions.
@@ -1304,8 +1296,6 @@ pub const Inst = struct {
.array_init_elem_ptr,
.validate_ref_ty,
.validate_const,
- .try_operand_ty,
- .try_ref_operand_ty,
.restore_err_ret_index_unconditional,
.restore_err_ret_index_fn_entry,
=> false,
@@ -1365,8 +1355,6 @@ pub const Inst = struct {
.validate_ptr_array_init,
.validate_ref_ty,
.validate_const,
- .try_operand_ty,
- .try_ref_operand_ty,
=> true,
.param,
@@ -1685,7 +1673,7 @@ pub const Inst = struct {
.func = .pl_node,
.func_inferred = .pl_node,
.func_fancy = .pl_node,
- .import = .str_tok,
+ .import = .pl_tok,
.int = .int,
.int_big = .str,
.float = .float,
@@ -1749,8 +1737,6 @@ pub const Inst = struct {
.coerce_ptr_elem_ty = .pl_node,
.validate_ref_ty = .un_tok,
.validate_const = .un_node,
- .try_operand_ty = .un_node,
- .try_ref_operand_ty = .un_node,
.int_from_ptr = .un_node,
.compile_error = .un_node,
@@ -2128,7 +2114,7 @@ pub const Inst = struct {
ref_start_index = static_len,
_,
- pub const static_len = 92;
+ pub const static_len = 93;
pub fn toRef(i: Index) Inst.Ref {
return @enumFromInt(@intFromEnum(Index.ref_start_index) + @intFromEnum(i));
@@ -2229,6 +2215,7 @@ pub const Inst = struct {
vector_4_u64_type,
vector_4_f16_type,
vector_8_f16_type,
+ vector_2_f32_type,
vector_4_f32_type,
vector_8_f32_type,
vector_2_f64_type,
@@ -3854,6 +3841,13 @@ pub const Inst = struct {
/// If `.none`, restore unconditionally.
operand: Ref,
};
+
+ pub const Import = struct {
+ /// The result type of the import, or `.none` if none was available.
+ res_ty: Ref,
+ /// The import path.
+ path: NullTerminatedString,
+ };
};
pub const SpecialProng = enum { none, @"else", under };
@@ -4196,8 +4190,6 @@ fn findTrackableInner(
.coerce_ptr_elem_ty,
.validate_ref_ty,
.validate_const,
- .try_operand_ty,
- .try_ref_operand_ty,
.struct_init_empty,
.struct_init_empty_result,
.struct_init_empty_ref_result,
diff --git a/lib/std/zig/Zoir.zig b/lib/std/zig/Zoir.zig
index 9a9bc41976..700bf6ea32 100644
--- a/lib/std/zig/Zoir.zig
+++ b/lib/std/zig/Zoir.zig
@@ -10,6 +10,31 @@ string_bytes: []u8,
compile_errors: []Zoir.CompileError,
error_notes: []Zoir.CompileError.Note,
+/// The data stored at byte offset 0 when ZOIR is stored in a file.
+pub const Header = extern struct {
+ nodes_len: u32,
+ extra_len: u32,
+ limbs_len: u32,
+ string_bytes_len: u32,
+ compile_errors_len: u32,
+ error_notes_len: u32,
+
+ /// We could leave this as padding, however it triggers a Valgrind warning because
+ /// we read and write undefined bytes to the file system. This is harmless, but
+ /// it's essentially free to have a zero field here and makes the warning go away,
+ /// making it more likely that following Valgrind warnings will be taken seriously.
+ unused: u64 = 0,
+
+ stat_inode: std.fs.File.INode,
+ stat_size: u64,
+ stat_mtime: i128,
+
+ comptime {
+ // Check that `unused` is working as expected
+ assert(std.meta.hasUniqueRepresentation(Header));
+ }
+};
+
pub fn hasCompileErrors(zoir: Zoir) bool {
if (zoir.compile_errors.len > 0) {
assert(zoir.nodes.len == 0);
@@ -54,7 +79,7 @@ pub const Node = union(enum) {
/// A floating-point literal.
float_literal: f128,
/// A Unicode codepoint literal.
- char_literal: u32,
+ char_literal: u21,
/// An enum literal. The string is the literal, i.e. `foo` for `.foo`.
enum_literal: NullTerminatedString,
/// A string literal.
@@ -96,7 +121,7 @@ pub const Node = union(enum) {
} } },
.float_literal_small => .{ .float_literal = @as(f32, @bitCast(repr.data)) },
.float_literal => .{ .float_literal = @bitCast(zoir.extra[repr.data..][0..4].*) },
- .char_literal => .{ .char_literal = repr.data },
+ .char_literal => .{ .char_literal = @intCast(repr.data) },
.enum_literal => .{ .enum_literal = @enumFromInt(repr.data) },
.string_literal => .{ .string_literal = s: {
const start, const len = zoir.extra[repr.data..][0..2].*;
diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig
index 7f85f35f05..c50cf83538 100644
--- a/lib/std/zig/ZonGen.zig
+++ b/lib/std/zig/ZonGen.zig
@@ -3,6 +3,8 @@
gpa: Allocator,
tree: Ast,
+options: Options,
+
nodes: std.MultiArrayList(Zoir.Node.Repr),
extra: std.ArrayListUnmanaged(u32),
limbs: std.ArrayListUnmanaged(std.math.big.Limb),
@@ -12,12 +14,21 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d
compile_errors: std.ArrayListUnmanaged(Zoir.CompileError),
error_notes: std.ArrayListUnmanaged(Zoir.CompileError.Note),
-pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zoir {
+pub const Options = struct {
+ /// When false, string literals are not parsed. `string_literal` nodes will contain empty
+ /// strings, and errors that normally occur during string parsing will not be raised.
+ ///
+ /// `parseStrLit` and `strLitSizeHint` may be used to parse string literals after the fact.
+ parse_str_lits: bool = true,
+};
+
+pub fn generate(gpa: Allocator, tree: Ast, options: Options) Allocator.Error!Zoir {
assert(tree.mode == .zon);
var zg: ZonGen = .{
.gpa = gpa,
.tree = tree,
+ .options = options,
.nodes = .empty,
.extra = .empty,
.limbs = .empty,
@@ -250,7 +261,20 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
.block_two_semicolon,
.block,
.block_semicolon,
- => try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}),
+ => {
+ const size = switch (node_tags[node]) {
+ .block_two, .block_two_semicolon => @intFromBool(node_datas[node].lhs != 0) + @intFromBool(node_datas[node].rhs != 0),
+ .block, .block_semicolon => node_datas[node].rhs - node_datas[node].lhs,
+ else => unreachable,
+ };
+ if (size == 0) {
+ try zg.addErrorNodeNotes(node, "void literals are not available in ZON", .{}, &.{
+ try zg.errNoteNode(node, "void union payloads can be represented by enum literals", .{}),
+ });
+ } else {
+ try zg.addErrorNode(node, "blocks are not allowed in ZON", .{});
+ }
+ },
.array_init_one,
.array_init_one_comma,
@@ -403,58 +427,37 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
.ast_node = node,
});
+ // For short initializers, track the names on the stack rather than going through gpa.
+ var sfba_state = std.heap.stackFallback(256, gpa);
+ const sfba = sfba_state.get();
+ var field_names: std.AutoHashMapUnmanaged(Zoir.NullTerminatedString, Ast.TokenIndex) = .empty;
+ defer field_names.deinit(sfba);
+
+ var reported_any_duplicate = false;
+
for (full.ast.fields, names_start.., first_elem..) |elem_node, extra_name_idx, elem_dest_node| {
const name_token = tree.firstToken(elem_node) - 2;
- zg.extra.items[extra_name_idx] = @intFromEnum(zg.identAsString(name_token) catch |err| switch (err) {
- error.BadString => undefined, // doesn't matter, there's an error
+ if (zg.identAsString(name_token)) |name_str| {
+ zg.extra.items[extra_name_idx] = @intFromEnum(name_str);
+ const gop = try field_names.getOrPut(sfba, name_str);
+ if (gop.found_existing and !reported_any_duplicate) {
+ reported_any_duplicate = true;
+ const earlier_token = gop.value_ptr.*;
+ try zg.addErrorTokNotes(earlier_token, "duplicate struct field name", .{}, &.{
+ try zg.errNoteTok(name_token, "duplicate name here", .{}),
+ });
+ }
+ gop.value_ptr.* = name_token;
+ } else |err| switch (err) {
+ error.BadString => {}, // there's an error, so it's fine to not populate `zg.extra`
error.OutOfMemory => |e| return e,
- });
+ }
try zg.expr(elem_node, @enumFromInt(elem_dest_node));
}
},
}
}
-fn parseStrLit(zg: *ZonGen, token: Ast.TokenIndex, offset: u32) !u32 {
- const raw_string = zg.tree.tokenSlice(token)[offset..];
- const start = zg.string_bytes.items.len;
- switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) {
- .success => return @intCast(start),
- .failure => |err| {
- try zg.lowerStrLitError(err, token, raw_string, offset);
- return error.BadString;
- },
- }
-}
-
-fn parseMultilineStrLit(zg: *ZonGen, node: Ast.Node.Index) !u32 {
- const gpa = zg.gpa;
- const tree = zg.tree;
- const string_bytes = &zg.string_bytes;
-
- const first_tok, const last_tok = bounds: {
- const node_data = tree.nodes.items(.data)[node];
- break :bounds .{ node_data.lhs, node_data.rhs };
- };
-
- const str_index: u32 = @intCast(string_bytes.items.len);
-
- // First line: do not append a newline.
- {
- const line_bytes = tree.tokenSlice(first_tok)[2..];
- try string_bytes.appendSlice(gpa, line_bytes);
- }
- // Following lines: each line prepends a newline.
- for (first_tok + 1..last_tok + 1) |tok_idx| {
- const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..];
- try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1);
- string_bytes.appendAssumeCapacity('\n');
- string_bytes.appendSliceAssumeCapacity(line_bytes);
- }
-
- return @intCast(str_index);
-}
-
fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
const tree = zg.tree;
assert(tree.tokens.items(.tag)[ident_token] == .identifier);
@@ -464,7 +467,18 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
try zg.string_bytes.appendSlice(zg.gpa, ident_name);
return @intCast(start);
} else {
- const start = try zg.parseStrLit(ident_token, 1);
+ const offset = 1;
+ const start: u32 = @intCast(zg.string_bytes.items.len);
+ const raw_string = zg.tree.tokenSlice(ident_token)[offset..];
+ try zg.string_bytes.ensureUnusedCapacity(zg.gpa, raw_string.len);
+ switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) {
+ .success => {},
+ .failure => |err| {
+ try zg.lowerStrLitError(err, ident_token, raw_string, offset);
+ return error.BadString;
+ },
+ }
+
const slice = zg.string_bytes.items[start..];
if (mem.indexOfScalar(u8, slice, 0) != null) {
try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{});
@@ -477,19 +491,93 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {
}
}
+/// Estimates the size of a string node without parsing it.
+pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize {
+ switch (tree.nodes.items(.tag)[node]) {
+ // Parsed string literals are typically around the size of the raw strings.
+ .string_literal => {
+ const token = tree.nodes.items(.main_token)[node];
+ const raw_string = tree.tokenSlice(token);
+ return raw_string.len;
+ },
+ // Multiline string literal lengths can be computed exactly.
+ .multiline_string_literal => {
+ const first_tok, const last_tok = bounds: {
+ const node_data = tree.nodes.items(.data)[node];
+ break :bounds .{ node_data.lhs, node_data.rhs };
+ };
+
+ var size = tree.tokenSlice(first_tok)[2..].len;
+ for (first_tok + 1..last_tok + 1) |tok_idx| {
+ size += 1; // Newline
+ size += tree.tokenSlice(@intCast(tok_idx))[2..].len;
+ }
+ return size;
+ },
+ else => unreachable,
+ }
+}
+
+/// Parses the given node as a string literal.
+pub fn parseStrLit(
+ tree: Ast,
+ node: Ast.Node.Index,
+ writer: anytype,
+) error{OutOfMemory}!std.zig.string_literal.Result {
+ switch (tree.nodes.items(.tag)[node]) {
+ .string_literal => {
+ const token = tree.nodes.items(.main_token)[node];
+ const raw_string = tree.tokenSlice(token);
+ return std.zig.string_literal.parseWrite(writer, raw_string);
+ },
+ .multiline_string_literal => {
+ const first_tok, const last_tok = bounds: {
+ const node_data = tree.nodes.items(.data)[node];
+ break :bounds .{ node_data.lhs, node_data.rhs };
+ };
+
+ // First line: do not append a newline.
+ {
+ const line_bytes = tree.tokenSlice(first_tok)[2..];
+ try writer.writeAll(line_bytes);
+ }
+
+ // Following lines: each line prepends a newline.
+ for (first_tok + 1..last_tok + 1) |tok_idx| {
+ const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..];
+ try writer.writeByte('\n');
+ try writer.writeAll(line_bytes);
+ }
+
+ return .success;
+ },
+ // Node must represent a string
+ else => unreachable,
+ }
+}
+
const StringLiteralResult = union(enum) {
nts: Zoir.NullTerminatedString,
slice: struct { start: u32, len: u32 },
};
fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult {
+ if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } };
+
const gpa = zg.gpa;
const string_bytes = &zg.string_bytes;
- const str_index = switch (zg.tree.nodes.items(.tag)[str_node]) {
- .string_literal => try zg.parseStrLit(zg.tree.nodes.items(.main_token)[str_node], 0),
- .multiline_string_literal => try zg.parseMultilineStrLit(str_node),
- else => unreachable,
- };
+ const str_index: u32 = @intCast(zg.string_bytes.items.len);
+ const size_hint = strLitSizeHint(zg.tree, str_node);
+ try string_bytes.ensureUnusedCapacity(zg.gpa, size_hint);
+ switch (try parseStrLit(zg.tree, str_node, zg.string_bytes.writer(zg.gpa))) {
+ .success => {},
+ .failure => |err| {
+ const token = zg.tree.nodes.items(.main_token)[str_node];
+ const raw_string = zg.tree.tokenSlice(token);
+ try zg.lowerStrLitError(err, token, raw_string, 0);
+ return error.BadString;
+ },
+ }
const key: []const u8 = string_bytes.items[str_index..];
if (std.mem.indexOfScalar(u8, key, 0) != null) return .{ .slice = .{
.start = str_index,
@@ -540,7 +628,7 @@ fn numberLiteral(zg: *ZonGen, num_node: Ast.Node.Index, src_node: Ast.Node.Index
if (unsigned_num == 0 and sign == .negative) {
try zg.addErrorTokNotes(num_token, "integer literal '-0' is ambiguous", .{}, &.{
try zg.errNoteTok(num_token, "use '0' for an integer zero", .{}),
- try zg.errNoteTok(num_token, "use '-0.0' for a flaoting-point signed zero", .{}),
+ try zg.errNoteTok(num_token, "use '-0.0' for a floating-point signed zero", .{}),
});
return;
}
@@ -679,8 +767,20 @@ fn setNode(zg: *ZonGen, dest: Zoir.Node.Index, repr: Zoir.Node.Repr) void {
zg.nodes.set(@intFromEnum(dest), repr);
}
-fn lowerStrLitError(zg: *ZonGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, raw_string: []const u8, offset: u32) Allocator.Error!void {
- return err.lower(raw_string, offset, ZonGen.addErrorTokOff, .{ zg, token });
+fn lowerStrLitError(
+ zg: *ZonGen,
+ err: std.zig.string_literal.Error,
+ token: Ast.TokenIndex,
+ raw_string: []const u8,
+ offset: u32,
+) Allocator.Error!void {
+ return ZonGen.addErrorTokOff(
+ zg,
+ token,
+ @intCast(offset + err.offset()),
+ "{}",
+ .{err.fmt(raw_string)},
+ );
}
fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) Allocator.Error!void {
diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig
index 716a9b90f0..2dff70d70b 100644
--- a/lib/std/zig/string_literal.zig
+++ b/lib/std/zig/string_literal.zig
@@ -39,40 +39,82 @@ pub const Error = union(enum) {
/// `''`. Not returned for string literals.
empty_char_literal,
- /// Returns `func(first_args[0], ..., first_args[n], offset + bad_idx, format, args)`.
- pub fn lower(
+ const FormatMessage = struct {
err: Error,
raw_string: []const u8,
- offset: u32,
- comptime func: anytype,
- first_args: anytype,
- ) @typeInfo(@TypeOf(func)).@"fn".return_type.? {
- switch (err) {
- inline else => |bad_index_or_void, tag| {
- const bad_index: u32 = switch (@TypeOf(bad_index_or_void)) {
- void => 0,
- else => @intCast(bad_index_or_void),
- };
- const fmt_str: []const u8, const args = switch (tag) {
- .invalid_escape_character => .{ "invalid escape character: '{c}'", .{raw_string[bad_index]} },
- .expected_hex_digit => .{ "expected hex digit, found '{c}'", .{raw_string[bad_index]} },
- .empty_unicode_escape_sequence => .{ "empty unicode escape sequence", .{} },
- .expected_hex_digit_or_rbrace => .{ "expected hex digit or '}}', found '{c}'", .{raw_string[bad_index]} },
- .invalid_unicode_codepoint => .{ "unicode escape does not correspond to a valid unicode scalar value", .{} },
- .expected_lbrace => .{ "expected '{{', found '{c}'", .{raw_string[bad_index]} },
- .expected_rbrace => .{ "expected '}}', found '{c}'", .{raw_string[bad_index]} },
- .expected_single_quote => .{ "expected singel quote ('), found '{c}'", .{raw_string[bad_index]} },
- .invalid_character => .{ "invalid byte in string or character literal: '{c}'", .{raw_string[bad_index]} },
- .empty_char_literal => .{ "empty character literal", .{} },
- };
- return @call(.auto, func, first_args ++ .{
- offset + bad_index,
- fmt_str,
- args,
- });
- },
+ };
+
+ fn formatMessage(
+ self: FormatMessage,
+ comptime f: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = f;
+ _ = options;
+ switch (self.err) {
+ .invalid_escape_character => |bad_index| try writer.print(
+ "invalid escape character: '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .expected_hex_digit => |bad_index| try writer.print(
+ "expected hex digit, found '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .empty_unicode_escape_sequence => try writer.writeAll(
+ "empty unicode escape sequence",
+ ),
+ .expected_hex_digit_or_rbrace => |bad_index| try writer.print(
+ "expected hex digit or '}}', found '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .invalid_unicode_codepoint => try writer.writeAll(
+ "unicode escape does not correspond to a valid unicode scalar value",
+ ),
+ .expected_lbrace => |bad_index| try writer.print(
+ "expected '{{', found '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .expected_rbrace => |bad_index| try writer.print(
+ "expected '}}', found '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .expected_single_quote => |bad_index| try writer.print(
+ "expected single quote ('), found '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .invalid_character => |bad_index| try writer.print(
+ "invalid byte in string or character literal: '{c}'",
+ .{self.raw_string[bad_index]},
+ ),
+ .empty_char_literal => try writer.writeAll(
+ "empty character literal",
+ ),
}
}
+
+ pub fn fmt(self: @This(), raw_string: []const u8) std.fmt.Formatter(formatMessage) {
+ return .{ .data = .{
+ .err = self,
+ .raw_string = raw_string,
+ } };
+ }
+
+ pub fn offset(err: Error) usize {
+ return switch (err) {
+ inline .invalid_escape_character,
+ .expected_hex_digit,
+ .empty_unicode_escape_sequence,
+ .expected_hex_digit_or_rbrace,
+ .invalid_unicode_codepoint,
+ .expected_lbrace,
+ .expected_rbrace,
+ .expected_single_quote,
+ .invalid_character,
+ => |n| n,
+ .empty_char_literal => 0,
+ };
+ }
};
/// Asserts the slice starts and ends with single-quotes.
diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig
index cb2390e132..bd97845732 100644
--- a/lib/std/zig/system/darwin.zig
+++ b/lib/std/zig/system/darwin.zig
@@ -37,15 +37,16 @@ pub fn isSdkInstalled(allocator: Allocator) bool {
pub fn getSdk(allocator: Allocator, target: Target) ?[]const u8 {
const is_simulator_abi = target.abi == .simulator;
const sdk = switch (target.os.tag) {
- .macos => "macosx",
.ios => switch (target.abi) {
- .simulator => "iphonesimulator",
.macabi => "macosx",
+ .simulator => "iphonesimulator",
else => "iphoneos",
},
- .watchos => if (is_simulator_abi) "watchsimulator" else "watchos",
+ .driverkit => "driverkit",
+ .macos => "macosx",
.tvos => if (is_simulator_abi) "appletvsimulator" else "appletvos",
.visionos => if (is_simulator_abi) "xrsimulator" else "xros",
+ .watchos => if (is_simulator_abi) "watchsimulator" else "watchos",
else => return null,
};
const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };
diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig
index fad198a4eb..5a36872017 100644
--- a/lib/std/zig/target.zig
+++ b/lib/std/zig/target.zig
@@ -195,12 +195,8 @@ pub fn isLibCLibName(target: std.Target, name: []const u8) bool {
return true;
if (eqlIgnoreCase(ignore_case, name, "ksguid"))
return true;
- if (eqlIgnoreCase(ignore_case, name, "ksuser"))
- return true;
if (eqlIgnoreCase(ignore_case, name, "largeint"))
return true;
- if (eqlIgnoreCase(ignore_case, name, "locationapi"))
- return true;
if (eqlIgnoreCase(ignore_case, name, "m"))
return true;
if (eqlIgnoreCase(ignore_case, name, "mfuuid"))
@@ -213,14 +209,8 @@ pub fn isLibCLibName(target: std.Target, name: []const u8) bool {
return true;
if (eqlIgnoreCase(ignore_case, name, "moldname"))
return true;
- if (eqlIgnoreCase(ignore_case, name, "msxml2"))
- return true;
- if (eqlIgnoreCase(ignore_case, name, "msxml6"))
- return true;
if (eqlIgnoreCase(ignore_case, name, "msvcrt-os"))
return true;
- if (eqlIgnoreCase(ignore_case, name, "ntoskrnl"))
- return true;
if (eqlIgnoreCase(ignore_case, name, "portabledeviceguids"))
return true;
if (eqlIgnoreCase(ignore_case, name, "pthread"))
diff --git a/lib/std/zon.zig b/lib/std/zon.zig
new file mode 100644
index 0000000000..252331057a
--- /dev/null
+++ b/lib/std/zon.zig
@@ -0,0 +1,45 @@
+//! ZON parsing and stringification.
+//!
+//! ZON ("Zig Object Notation") is a textual file format. Outside of `nan` and `inf` literals, ZON's
+//! grammar is a subset of Zig's.
+//!
+//! Supported Zig primitives:
+//! * boolean literals
+//! * number literals (including `nan` and `inf`)
+//! * character literals
+//! * enum literals
+//! * `null` literals
+//! * string literals
+//! * multiline string literals
+//!
+//! Supported Zig container types:
+//! * anonymous struct literals
+//! * anonymous tuple literals
+//!
+//! Here is an example ZON object:
+//! ```
+//! .{
+//! .a = 1.5,
+//! .b = "hello, world!",
+//! .c = .{ true, false },
+//! .d = .{ 1, 2, 3 },
+//! }
+//! ```
+//!
+//! Individual primitives are also valid ZON, for example:
+//! ```
+//! "This string is a valid ZON object."
+//! ```
+//!
+//! ZON may not contain type names.
+//!
+//! ZON does not have syntax for pointers, but the parsers will allocate as needed to match the
+//! given Zig types. Similarly, the serializer will traverse pointers.
+
+pub const parse = @import("zon/parse.zig");
+pub const stringify = @import("zon/stringify.zig");
+
+test {
+ _ = parse;
+ _ = stringify;
+}
diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig
new file mode 100644
index 0000000000..daf83d0bbd
--- /dev/null
+++ b/lib/std/zon/parse.zig
@@ -0,0 +1,3449 @@
+//! The simplest way to parse ZON at runtime is to use `fromSlice`. If you need to parse ZON at
+//! compile time, you may use `@import`.
+//!
+//! Parsing from individual Zoir nodes is also available:
+//! * `fromZoir`
+//! * `fromZoirNode`
+//!
+//! For lower level control, it is possible to operate on `std.zig.Zoir` directly.
+
+const std = @import("std");
+const builtin = @import("builtin");
+const Allocator = std.mem.Allocator;
+const Ast = std.zig.Ast;
+const Zoir = std.zig.Zoir;
+const ZonGen = std.zig.ZonGen;
+const TokenIndex = std.zig.Ast.TokenIndex;
+const Base = std.zig.number_literal.Base;
+const StrLitErr = std.zig.string_literal.Error;
+const NumberLiteralError = std.zig.number_literal.Error;
+const assert = std.debug.assert;
+const ArrayListUnmanaged = std.ArrayListUnmanaged;
+
+/// Rename when adding or removing support for a type.
+const valid_types = {};
+
+/// Configuration for the runtime parser.
+pub const Options = struct {
+ /// If true, unknown fields do not error.
+ ignore_unknown_fields: bool = false,
+ /// If true, the parser cleans up partially parsed values on error. This requires some extra
+ /// bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because
+ /// you're using arena allocation.)
+ free_on_error: bool = true,
+};
+
+pub const Error = union(enum) {
+ zoir: Zoir.CompileError,
+ type_check: Error.TypeCheckFailure,
+
+ pub const Note = union(enum) {
+ zoir: Zoir.CompileError.Note,
+ type_check: TypeCheckFailure.Note,
+
+ pub const Iterator = struct {
+ index: usize = 0,
+ err: Error,
+ status: *const Status,
+
+ pub fn next(self: *@This()) ?Note {
+ switch (self.err) {
+ .zoir => |err| {
+ if (self.index >= err.note_count) return null;
+ const zoir = self.status.zoir.?;
+ const note = err.getNotes(zoir)[self.index];
+ self.index += 1;
+ return .{ .zoir = note };
+ },
+ .type_check => |err| {
+ if (self.index >= err.getNoteCount()) return null;
+ const note = err.getNote(self.index);
+ self.index += 1;
+ return .{ .type_check = note };
+ },
+ }
+ }
+ };
+
+ fn formatMessage(
+ self: []const u8,
+ comptime f: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = f;
+ _ = options;
+
+ // Just writes the string for now, but we're keeping this behind a formatter so we have
+ // the option to extend it in the future to print more advanced messages (like `Error`
+ // does) without breaking the API.
+ try writer.writeAll(self);
+ }
+
+ pub fn fmtMessage(self: Note, status: *const Status) std.fmt.Formatter(Note.formatMessage) {
+ return .{ .data = switch (self) {
+ .zoir => |note| note.msg.get(status.zoir.?),
+ .type_check => |note| note.msg,
+ } };
+ }
+
+ pub fn getLocation(self: Note, status: *const Status) Ast.Location {
+ const ast = status.ast.?;
+ switch (self) {
+ .zoir => |note| return zoirErrorLocation(ast, note.token, note.node_or_offset),
+ .type_check => |note| return ast.tokenLocation(note.offset, note.token),
+ }
+ }
+ };
+
+ pub const Iterator = struct {
+ index: usize = 0,
+ status: *const Status,
+
+ pub fn next(self: *@This()) ?Error {
+ const zoir = self.status.zoir orelse return null;
+
+ if (self.index < zoir.compile_errors.len) {
+ const result: Error = .{ .zoir = zoir.compile_errors[self.index] };
+ self.index += 1;
+ return result;
+ }
+
+ if (self.status.type_check) |err| {
+ if (self.index == zoir.compile_errors.len) {
+ const result: Error = .{ .type_check = err };
+ self.index += 1;
+ return result;
+ }
+ }
+
+ return null;
+ }
+ };
+
+ const TypeCheckFailure = struct {
+ const Note = struct {
+ token: Ast.TokenIndex,
+ offset: u32,
+ msg: []const u8,
+ owned: bool,
+
+ fn deinit(self: @This(), gpa: Allocator) void {
+ if (self.owned) gpa.free(self.msg);
+ }
+ };
+
+ message: []const u8,
+ owned: bool,
+ token: Ast.TokenIndex,
+ offset: u32,
+ note: ?@This().Note,
+
+ fn deinit(self: @This(), gpa: Allocator) void {
+ if (self.note) |note| note.deinit(gpa);
+ if (self.owned) gpa.free(self.message);
+ }
+
+ fn getNoteCount(self: @This()) usize {
+ return @intFromBool(self.note != null);
+ }
+
+ fn getNote(self: @This(), index: usize) @This().Note {
+ assert(index == 0);
+ return self.note.?;
+ }
+ };
+
+ const FormatMessage = struct {
+ err: Error,
+ status: *const Status,
+ };
+
+ fn formatMessage(
+ self: FormatMessage,
+ comptime f: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = f;
+ _ = options;
+ switch (self.err) {
+ .zoir => |err| try writer.writeAll(err.msg.get(self.status.zoir.?)),
+ .type_check => |tc| try writer.writeAll(tc.message),
+ }
+ }
+
+ pub fn fmtMessage(self: @This(), status: *const Status) std.fmt.Formatter(formatMessage) {
+ return .{ .data = .{
+ .err = self,
+ .status = status,
+ } };
+ }
+
+ pub fn getLocation(self: @This(), status: *const Status) Ast.Location {
+ const ast = status.ast.?;
+ return switch (self) {
+ .zoir => |err| return zoirErrorLocation(
+ status.ast.?,
+ err.token,
+ err.node_or_offset,
+ ),
+ .type_check => |err| return ast.tokenLocation(err.offset, err.token),
+ };
+ }
+
+ pub fn iterateNotes(self: @This(), status: *const Status) Note.Iterator {
+ return .{ .err = self, .status = status };
+ }
+
+ fn zoirErrorLocation(ast: Ast, maybe_token: Ast.TokenIndex, node_or_offset: u32) Ast.Location {
+ if (maybe_token == Zoir.CompileError.invalid_token) {
+ const main_tokens = ast.nodes.items(.main_token);
+ const ast_node = node_or_offset;
+ const token = main_tokens[ast_node];
+ return ast.tokenLocation(0, token);
+ } else {
+ var location = ast.tokenLocation(0, maybe_token);
+ location.column += node_or_offset;
+ return location;
+ }
+ }
+};
+
+/// Information about the success or failure of a parse.
+pub const Status = struct {
+ ast: ?Ast = null,
+ zoir: ?Zoir = null,
+ type_check: ?Error.TypeCheckFailure = null,
+
+ fn assertEmpty(self: Status) void {
+ assert(self.ast == null);
+ assert(self.zoir == null);
+ assert(self.type_check == null);
+ }
+
+ pub fn deinit(self: *Status, gpa: Allocator) void {
+ if (self.ast) |*ast| ast.deinit(gpa);
+ if (self.zoir) |*zoir| zoir.deinit(gpa);
+ if (self.type_check) |tc| tc.deinit(gpa);
+ self.* = undefined;
+ }
+
+ pub fn iterateErrors(self: *const Status) Error.Iterator {
+ return .{ .status = self };
+ }
+
+ pub fn format(
+ self: *const @This(),
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) !void {
+ _ = fmt;
+ _ = options;
+ var errors = self.iterateErrors();
+ while (errors.next()) |err| {
+ const loc = err.getLocation(self);
+ const msg = err.fmtMessage(self);
+ try writer.print("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, msg });
+
+ var notes = err.iterateNotes(self);
+ while (notes.next()) |note| {
+ const note_loc = note.getLocation(self);
+ const note_msg = note.fmtMessage(self);
+ try writer.print("{}:{}: note: {s}\n", .{
+ note_loc.line + 1,
+ note_loc.column + 1,
+ note_msg,
+ });
+ }
+ }
+ }
+};
+
+/// Parses the given slice as ZON.
+///
+/// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is
+/// invalid or can not be deserialized into type `T`.
+///
+/// When the parser returns `error.ParseZon`, it will also store a human readable explanation in
+/// `status` if non null. If status is not null, it must be initialized to `.{}`.
+pub fn fromSlice(
+ /// The type to deserialize into. May not be or contain any of the following types:
+ /// * Any comptime-only type, except in a comptime field
+ /// * `type`
+ /// * `void`, except as a union payload
+ /// * `noreturn`
+ /// * An error set/error union
+ /// * A many-pointer or C-pointer
+ /// * An opaque type, including `anyopaque`
+ /// * An async frame type, including `anyframe` and `anyframe->T`
+ /// * A function
+ ///
+ /// All other types are valid. Unsupported types will fail at compile time.
+ T: type,
+ gpa: Allocator,
+ source: [:0]const u8,
+ status: ?*Status,
+ options: Options,
+) error{ OutOfMemory, ParseZon }!T {
+ if (status) |s| s.assertEmpty();
+
+ var ast = try std.zig.Ast.parse(gpa, source, .zon);
+ defer if (status == null) ast.deinit(gpa);
+ if (status) |s| s.ast = ast;
+
+ // If there's no status, Zoir exists for the lifetime of this function. If there is a status,
+ // ownership is transferred to status.
+ var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
+ defer if (status == null) zoir.deinit(gpa);
+
+ if (status) |s| s.* = .{};
+ return fromZoir(T, gpa, ast, zoir, status, options);
+}
+
+/// Like `fromSlice`, but operates on `Zoir` instead of ZON source.
+pub fn fromZoir(
+ T: type,
+ gpa: Allocator,
+ ast: Ast,
+ zoir: Zoir,
+ status: ?*Status,
+ options: Options,
+) error{ OutOfMemory, ParseZon }!T {
+ return fromZoirNode(T, gpa, ast, zoir, .root, status, options);
+}
+
+/// Like `fromZoir`, but the parse starts on `node` instead of root.
+pub fn fromZoirNode(
+ T: type,
+ gpa: Allocator,
+ ast: Ast,
+ zoir: Zoir,
+ node: Zoir.Node.Index,
+ status: ?*Status,
+ options: Options,
+) error{ OutOfMemory, ParseZon }!T {
+ comptime assert(canParseType(T));
+
+ if (status) |s| {
+ s.assertEmpty();
+ s.ast = ast;
+ s.zoir = zoir;
+ }
+
+ if (zoir.hasCompileErrors()) {
+ return error.ParseZon;
+ }
+
+ var parser: Parser = .{
+ .gpa = gpa,
+ .ast = ast,
+ .zoir = zoir,
+ .options = options,
+ .status = status,
+ };
+
+ return parser.parseExpr(T, node);
+}
+
+/// Frees ZON values.
+///
+/// Provided for convenience, you may also free these values on your own using the same allocator
+/// passed into the parser.
+///
+/// Asserts at comptime that sufficient information is available via the type system to free this
+/// value. Untagged unions, for example, will fail this assert.
+pub fn free(gpa: Allocator, value: anytype) void {
+ const Value = @TypeOf(value);
+
+ _ = valid_types;
+ switch (@typeInfo(Value)) {
+ .bool, .int, .float, .@"enum" => {},
+ .pointer => |pointer| {
+ switch (pointer.size) {
+ .one => {
+ free(gpa, value.*);
+ gpa.destroy(value);
+ },
+ .slice => {
+ for (value) |item| {
+ free(gpa, item);
+ }
+ gpa.free(value);
+ },
+ .many, .c => comptime unreachable,
+ }
+ },
+ .array => for (value) |item| {
+ free(gpa, item);
+ },
+ .@"struct" => |@"struct"| inline for (@"struct".fields) |field| {
+ free(gpa, @field(value, field.name));
+ },
+ .@"union" => |@"union"| if (@"union".tag_type == null) {
+ if (comptime requiresAllocator(Value)) unreachable;
+ } else switch (value) {
+ inline else => |_, tag| {
+ free(gpa, @field(value, @tagName(tag)));
+ },
+ },
+ .optional => if (value) |some| {
+ free(gpa, some);
+ },
+ .vector => |vector| for (0..vector.len) |i| free(gpa, value[i]),
+ .void => {},
+ else => comptime unreachable,
+ }
+}
+
+fn requiresAllocator(T: type) bool {
+ _ = valid_types;
+ return switch (@typeInfo(T)) {
+ .pointer => true,
+ .array => |array| return array.len > 0 and requiresAllocator(array.child),
+ .@"struct" => |@"struct"| inline for (@"struct".fields) |field| {
+ if (requiresAllocator(field.type)) {
+ break true;
+ }
+ } else false,
+ .@"union" => |@"union"| inline for (@"union".fields) |field| {
+ if (requiresAllocator(field.type)) {
+ break true;
+ }
+ } else false,
+ .optional => |optional| requiresAllocator(optional.child),
+ .vector => |vector| return vector.len > 0 and requiresAllocator(vector.child),
+ else => false,
+ };
+}
+
+const Parser = struct {
+ gpa: Allocator,
+ ast: Ast,
+ zoir: Zoir,
+ status: ?*Status,
+ options: Options,
+
+ fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T {
+ return self.parseExprInner(T, node) catch |err| switch (err) {
+ error.WrongType => return self.failExpectedType(T, node),
+ else => |e| return e,
+ };
+ }
+
+ fn parseExprInner(
+ self: *@This(),
+ T: type,
+ node: Zoir.Node.Index,
+ ) error{ ParseZon, OutOfMemory, WrongType }!T {
+ switch (@typeInfo(T)) {
+ .optional => |optional| if (node.get(self.zoir) == .null) {
+ return null;
+ } else {
+ return try self.parseExprInner(optional.child, node);
+ },
+ .bool => return self.parseBool(node),
+ .int => return self.parseInt(T, node),
+ .float => return self.parseFloat(T, node),
+ .@"enum" => return self.parseEnumLiteral(T, node),
+ .pointer => |pointer| switch (pointer.size) {
+ .one => {
+ const result = try self.gpa.create(pointer.child);
+ errdefer self.gpa.destroy(result);
+ result.* = try self.parseExprInner(pointer.child, node);
+ return result;
+ },
+ .slice => return self.parseSlicePointer(T, node),
+ else => comptime unreachable,
+ },
+ .array => return self.parseArray(T, node),
+ .@"struct" => |@"struct"| if (@"struct".is_tuple)
+ return self.parseTuple(T, node)
+ else
+ return self.parseStruct(T, node),
+ .@"union" => return self.parseUnion(T, node),
+ .vector => return self.parseVector(T, node),
+
+ else => comptime unreachable,
+ }
+ }
+
+ /// Prints a message of the form `expected T` where T is first converted to a ZON type. For
+ /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just
+ /// referred to by the type of container.
+ fn failExpectedType(
+ self: @This(),
+ T: type,
+ node: Zoir.Node.Index,
+ ) error{ ParseZon, OutOfMemory } {
+ @branchHint(.cold);
+ return self.failExpectedTypeInner(T, false, node);
+ }
+
+ fn failExpectedTypeInner(
+ self: @This(),
+ T: type,
+ opt: bool,
+ node: Zoir.Node.Index,
+ ) error{ ParseZon, OutOfMemory } {
+ _ = valid_types;
+ switch (@typeInfo(T)) {
+ .@"struct" => |@"struct"| if (@"struct".is_tuple) {
+ if (opt) {
+ return self.failNode(node, "expected optional tuple");
+ } else {
+ return self.failNode(node, "expected tuple");
+ }
+ } else {
+ if (opt) {
+ return self.failNode(node, "expected optional struct");
+ } else {
+ return self.failNode(node, "expected struct");
+ }
+ },
+ .@"union" => if (opt) {
+ return self.failNode(node, "expected optional union");
+ } else {
+ return self.failNode(node, "expected union");
+ },
+ .array => if (opt) {
+ return self.failNode(node, "expected optional array");
+ } else {
+ return self.failNode(node, "expected array");
+ },
+ .pointer => |pointer| switch (pointer.size) {
+ .one => return self.failExpectedTypeInner(pointer.child, opt, node),
+ .slice => {
+ if (pointer.child == u8 and
+ pointer.is_const and
+ (pointer.sentinel() == null or pointer.sentinel() == 0) and
+ pointer.alignment == 1)
+ {
+ if (opt) {
+ return self.failNode(node, "expected optional string");
+ } else {
+ return self.failNode(node, "expected string");
+ }
+ } else {
+ if (opt) {
+ return self.failNode(node, "expected optional array");
+ } else {
+ return self.failNode(node, "expected array");
+ }
+ }
+ },
+ else => comptime unreachable,
+ },
+ .vector, .bool, .int, .float => if (opt) {
+ return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)});
+ } else {
+ return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)});
+ },
+ .@"enum" => if (opt) {
+ return self.failNode(node, "expected optional enum literal");
+ } else {
+ return self.failNode(node, "expected enum literal");
+ },
+ .optional => |optional| {
+ return self.failExpectedTypeInner(optional.child, true, node);
+ },
+ else => comptime unreachable,
+ }
+ }
+
+ fn parseBool(self: @This(), node: Zoir.Node.Index) !bool {
+ switch (node.get(self.zoir)) {
+ .true => return true,
+ .false => return false,
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T {
+ switch (node.get(self.zoir)) {
+ .int_literal => |int| switch (int) {
+ .small => |val| return std.math.cast(T, val) orelse
+ self.failCannotRepresent(T, node),
+ .big => |val| return val.toInt(T) catch
+ self.failCannotRepresent(T, node),
+ },
+ .float_literal => |val| return intFromFloatExact(T, val) orelse
+ self.failCannotRepresent(T, node),
+
+ .char_literal => |val| return std.math.cast(T, val) orelse
+ self.failCannotRepresent(T, node),
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T {
+ switch (node.get(self.zoir)) {
+ .int_literal => |int| switch (int) {
+ .small => |val| return @floatFromInt(val),
+ .big => |val| return val.toFloat(T),
+ },
+ .float_literal => |val| return @floatCast(val),
+ .pos_inf => return std.math.inf(T),
+ .neg_inf => return -std.math.inf(T),
+ .nan => return std.math.nan(T),
+ .char_literal => |val| return @floatFromInt(val),
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T {
+ switch (node.get(self.zoir)) {
+ .enum_literal => |field_name| {
+ // Create a comptime string map for the enum fields
+ const enum_fields = @typeInfo(T).@"enum".fields;
+ comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined;
+ inline for (enum_fields, 0..) |field, i| {
+ kvs_list[i] = .{ field.name, @enumFromInt(field.value) };
+ }
+ const enum_tags = std.StaticStringMap(T).initComptime(kvs_list);
+
+ // Get the tag if it exists
+ const field_name_str = field_name.get(self.zoir);
+ return enum_tags.get(field_name_str) orelse
+ self.failUnexpected(T, "enum literal", node, null, field_name_str);
+ },
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ switch (node.get(self.zoir)) {
+ .string_literal => return self.parseString(T, node),
+ .array_literal => |nodes| return self.parseSlice(T, nodes),
+ .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }),
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ const ast_node = node.getAstNode(self.zoir);
+ const pointer = @typeInfo(T).pointer;
+ var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node);
+ if (pointer.sentinel() != null) size_hint += 1;
+
+ var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint);
+ defer buf.deinit(self.gpa);
+ switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) {
+ .success => {},
+ .failure => |err| {
+ const token = self.ast.nodes.items(.main_token)[ast_node];
+ const raw_string = self.ast.tokenSlice(token);
+ return self.failTokenFmt(token, @intCast(err.offset()), "{s}", .{err.fmt(raw_string)});
+ },
+ }
+
+ if (pointer.child != u8 or
+ pointer.size != .slice or
+ !pointer.is_const or
+ (pointer.sentinel() != null and pointer.sentinel() != 0) or
+ pointer.alignment != 1)
+ {
+ return error.WrongType;
+ }
+
+ if (pointer.sentinel() != null) {
+ return buf.toOwnedSliceSentinel(self.gpa, 0);
+ } else {
+ return buf.toOwnedSlice(self.gpa);
+ }
+ }
+
+ fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T {
+ const pointer = @typeInfo(T).pointer;
+
+ // Make sure we're working with a slice
+ switch (pointer.size) {
+ .slice => {},
+ .one, .many, .c => comptime unreachable,
+ }
+
+ // Allocate the slice
+ const slice = try self.gpa.allocWithOptions(
+ pointer.child,
+ nodes.len,
+ pointer.alignment,
+ pointer.sentinel(),
+ );
+ errdefer self.gpa.free(slice);
+
+ // Parse the elements and return the slice
+ for (slice, 0..) |*elem, i| {
+ errdefer if (self.options.free_on_error) {
+ for (slice[0..i]) |item| {
+ free(self.gpa, item);
+ }
+ };
+ elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i)));
+ }
+
+ return slice;
+ }
+
+ fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) {
+ .array_literal => |nodes| nodes,
+ .empty_literal => .{ .start = node, .len = 0 },
+ else => return error.WrongType,
+ };
+
+ const array_info = @typeInfo(T).array;
+
+ // Check if the size matches
+ if (nodes.len < array_info.len) {
+ return self.failNodeFmt(
+ node,
+ "expected {} array elements; found {}",
+ .{ array_info.len, nodes.len },
+ );
+ } else if (nodes.len > array_info.len) {
+ return self.failNodeFmt(
+ nodes.at(array_info.len),
+ "index {} outside of array of length {}",
+ .{ array_info.len, array_info.len },
+ );
+ }
+
+ // Parse the elements and return the array
+ var result: T = undefined;
+ for (&result, 0..) |*elem, i| {
+ // If we fail to parse this field, free all fields before it
+ errdefer if (self.options.free_on_error) {
+ for (result[0..i]) |item| {
+ free(self.gpa, item);
+ }
+ };
+
+ elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i)));
+ }
+ return result;
+ }
+
+ fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ const repr = node.get(self.zoir);
+ const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
+ .struct_literal => |nodes| nodes,
+ .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
+ else => return error.WrongType,
+ };
+
+ const field_infos = @typeInfo(T).@"struct".fields;
+
+ // Build a map from field name to index.
+ // The special value `comptime_field` indicates that this is actually a comptime field.
+ const comptime_field = std.math.maxInt(usize);
+ const field_indices: std.StaticStringMap(usize) = comptime b: {
+ var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined;
+ for (&kvs_list, field_infos, 0..) |*kv, field, i| {
+ kv.* = .{ field.name, if (field.is_comptime) comptime_field else i };
+ }
+ break :b .initComptime(kvs_list);
+ };
+
+ // Parse the struct
+ var result: T = undefined;
+ var field_found: [field_infos.len]bool = @splat(false);
+
+ // If we fail partway through, free all already initialized fields
+ var initialized: usize = 0;
+ errdefer if (self.options.free_on_error and field_infos.len > 0) {
+ for (fields.names[0..initialized]) |name_runtime| {
+ switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) {
+ inline 0...(field_infos.len - 1) => |name_index| {
+ const name = field_infos[name_index].name;
+ free(self.gpa, @field(result, name));
+ },
+ else => unreachable, // Can't be out of bounds
+ }
+ }
+ };
+
+ // Fill in the fields we found
+ for (0..fields.names.len) |i| {
+ const name = fields.names[i].get(self.zoir);
+ const field_index = field_indices.get(name) orelse {
+ if (self.options.ignore_unknown_fields) continue;
+ return self.failUnexpected(T, "field", node, i, name);
+ };
+ if (field_index == comptime_field) {
+ return self.failComptimeField(node, i);
+ }
+
+ // Mark the field as found. Assert that the found array is not zero length to satisfy
+ // the type checker (it can't be since we made it into an iteration of this loop.)
+ if (field_found.len == 0) unreachable;
+ field_found[field_index] = true;
+
+ switch (field_index) {
+ inline 0...(field_infos.len - 1) => |j| {
+ if (field_infos[j].is_comptime) unreachable;
+
+ @field(result, field_infos[j].name) = try self.parseExpr(
+ field_infos[j].type,
+ fields.vals.at(@intCast(i)),
+ );
+ },
+ else => unreachable, // Can't be out of bounds
+ }
+
+ initialized += 1;
+ }
+
+ // Fill in any missing default fields
+ inline for (field_found, 0..) |found, i| {
+ if (!found) {
+ const field_info = field_infos[i];
+ if (field_info.default_value_ptr) |default| {
+ const typed: *const field_info.type = @ptrCast(@alignCast(default));
+ @field(result, field_info.name) = typed.*;
+ } else {
+ return self.failNodeFmt(
+ node,
+ "missing required field {s}",
+ .{field_infos[i].name},
+ );
+ }
+ }
+ }
+
+ return result;
+ }
+
+ fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) {
+ .array_literal => |nodes| nodes,
+ .empty_literal => .{ .start = node, .len = 0 },
+ else => return error.WrongType,
+ };
+
+ var result: T = undefined;
+ const field_infos = @typeInfo(T).@"struct".fields;
+
+ if (nodes.len > field_infos.len) {
+ return self.failNodeFmt(
+ nodes.at(field_infos.len),
+ "index {} outside of tuple length {}",
+ .{ field_infos.len, field_infos.len },
+ );
+ }
+
+ inline for (0..field_infos.len) |i| {
+ // Check if we're out of bounds
+ if (i >= nodes.len) {
+ if (field_infos[i].default_value_ptr) |default| {
+ const typed: *const field_infos[i].type = @ptrCast(@alignCast(default));
+ @field(result, field_infos[i].name) = typed.*;
+ } else {
+ return self.failNodeFmt(node, "missing tuple field with index {}", .{i});
+ }
+ } else {
+ // If we fail to parse this field, free all fields before it
+ errdefer if (self.options.free_on_error) {
+ inline for (0..i) |j| {
+ if (j >= i) break;
+ free(self.gpa, result[j]);
+ }
+ };
+
+ if (field_infos[i].is_comptime) {
+ return self.failComptimeField(node, i);
+ } else {
+ result[i] = try self.parseExpr(field_infos[i].type, nodes.at(i));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T {
+ const @"union" = @typeInfo(T).@"union";
+ const field_infos = @"union".fields;
+
+ if (field_infos.len == 0) comptime unreachable;
+
+ // Gather info on the fields
+ const field_indices = b: {
+ comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined;
+ inline for (field_infos, 0..) |field, i| {
+ kvs_list[i] = .{ field.name, i };
+ }
+ break :b std.StaticStringMap(usize).initComptime(kvs_list);
+ };
+
+ // Parse the union
+ switch (node.get(self.zoir)) {
+ .enum_literal => |field_name| {
+ // The union must be tagged for an enum literal to coerce to it
+ if (@"union".tag_type == null) {
+ return error.WrongType;
+ }
+
+ // Get the index of the named field. We don't use `parseEnum` here as
+ // the order of the enum and the order of the union might not match!
+ const field_index = b: {
+ const field_name_str = field_name.get(self.zoir);
+ break :b field_indices.get(field_name_str) orelse
+ return self.failUnexpected(T, "field", node, null, field_name_str);
+ };
+
+ // Initialize the union from the given field.
+ switch (field_index) {
+ inline 0...field_infos.len - 1 => |i| {
+ // Fail if the field is not void
+ if (field_infos[i].type != void)
+ return self.failNode(node, "expected union");
+
+ // Instantiate the union
+ return @unionInit(T, field_infos[i].name, {});
+ },
+ else => unreachable, // Can't be out of bounds
+ }
+ },
+ .struct_literal => |struct_fields| {
+ if (struct_fields.names.len != 1) {
+ return error.WrongType;
+ }
+
+ // Fill in the field we found
+ const field_name = struct_fields.names[0];
+ const field_name_str = field_name.get(self.zoir);
+ const field_val = struct_fields.vals.at(0);
+ const field_index = field_indices.get(field_name_str) orelse
+ return self.failUnexpected(T, "field", node, 0, field_name_str);
+
+ switch (field_index) {
+ inline 0...field_infos.len - 1 => |i| {
+ if (field_infos[i].type == void) {
+ return self.failNode(field_val, "expected type 'void'");
+ } else {
+ const value = try self.parseExpr(field_infos[i].type, field_val);
+ return @unionInit(T, field_infos[i].name, value);
+ }
+ },
+ else => unreachable, // Can't be out of bounds
+ }
+ },
+ else => return error.WrongType,
+ }
+ }
+
+ fn parseVector(
+ self: *@This(),
+ T: type,
+ node: Zoir.Node.Index,
+ ) !T {
+ const vector_info = @typeInfo(T).vector;
+
+ const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) {
+ .array_literal => |nodes| nodes,
+ .empty_literal => .{ .start = node, .len = 0 },
+ else => return error.WrongType,
+ };
+
+ var result: T = undefined;
+
+ if (nodes.len != vector_info.len) {
+ return self.failNodeFmt(
+ node,
+ "expected {} vector elements; found {}",
+ .{ vector_info.len, nodes.len },
+ );
+ }
+
+ for (0..vector_info.len) |i| {
+ errdefer for (0..i) |j| free(self.gpa, result[j]);
+ result[i] = try self.parseExpr(vector_info.child, nodes.at(@intCast(i)));
+ }
+
+ return result;
+ }
+
+ fn failTokenFmt(
+ self: @This(),
+ token: Ast.TokenIndex,
+ offset: u32,
+ comptime fmt: []const u8,
+ args: anytype,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ return self.failTokenFmtNote(token, offset, fmt, args, null);
+ }
+
+ fn failTokenFmtNote(
+ self: @This(),
+ token: Ast.TokenIndex,
+ offset: u32,
+ comptime fmt: []const u8,
+ args: anytype,
+ note: ?Error.TypeCheckFailure.Note,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ comptime assert(args.len > 0);
+ if (self.status) |s| s.type_check = .{
+ .token = token,
+ .offset = offset,
+ .message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| {
+ if (note) |n| n.deinit(self.gpa);
+ return err;
+ },
+ .owned = true,
+ .note = note,
+ };
+ return error.ParseZon;
+ }
+
+ fn failNodeFmt(
+ self: @This(),
+ node: Zoir.Node.Index,
+ comptime fmt: []const u8,
+ args: anytype,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ const main_tokens = self.ast.nodes.items(.main_token);
+ const token = main_tokens[node.getAstNode(self.zoir)];
+ return self.failTokenFmt(token, 0, fmt, args);
+ }
+
+ fn failToken(
+ self: @This(),
+ failure: Error.TypeCheckFailure,
+ ) error{ParseZon} {
+ @branchHint(.cold);
+ if (self.status) |s| s.type_check = failure;
+ return error.ParseZon;
+ }
+
+ fn failNode(
+ self: @This(),
+ node: Zoir.Node.Index,
+ message: []const u8,
+ ) error{ParseZon} {
+ @branchHint(.cold);
+ const main_tokens = self.ast.nodes.items(.main_token);
+ const token = main_tokens[node.getAstNode(self.zoir)];
+ return self.failToken(.{
+ .token = token,
+ .offset = 0,
+ .message = message,
+ .owned = false,
+ .note = null,
+ });
+ }
+
+ fn failCannotRepresent(
+ self: @This(),
+ T: type,
+ node: Zoir.Node.Index,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)});
+ }
+
+ fn failUnexpected(
+ self: @This(),
+ T: type,
+ item_kind: []const u8,
+ node: Zoir.Node.Index,
+ field: ?usize,
+ name: []const u8,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ const token = if (field) |f| b: {
+ var buf: [2]Ast.Node.Index = undefined;
+ const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?;
+ const field_node = struct_init.ast.fields[f];
+ break :b self.ast.firstToken(field_node) - 2;
+ } else b: {
+ const main_tokens = self.ast.nodes.items(.main_token);
+ break :b main_tokens[node.getAstNode(self.zoir)];
+ };
+ switch (@typeInfo(T)) {
+ inline .@"struct", .@"union", .@"enum" => |info| {
+ const note: Error.TypeCheckFailure.Note = if (info.fields.len == 0) b: {
+ break :b .{
+ .token = token,
+ .offset = 0,
+ .msg = "none expected",
+ .owned = false,
+ };
+ } else b: {
+ const msg = "supported: ";
+ var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64);
+ defer buf.deinit(self.gpa);
+ const writer = buf.writer(self.gpa);
+ try writer.writeAll(msg);
+ inline for (info.fields, 0..) |field_info, i| {
+ if (i != 0) try writer.writeAll(", ");
+ try writer.print("'{p_}'", .{std.zig.fmtId(field_info.name)});
+ }
+ break :b .{
+ .token = token,
+ .offset = 0,
+ .msg = try buf.toOwnedSlice(self.gpa),
+ .owned = true,
+ };
+ };
+ return self.failTokenFmtNote(
+ token,
+ 0,
+ "unexpected {s} '{s}'",
+ .{ item_kind, name },
+ note,
+ );
+ },
+ else => comptime unreachable,
+ }
+ }
+
+ // Technically we could do this if we were willing to do a deep equal to verify
+ // the value matched, but doing so doesn't seem to support any real use cases
+ // so isn't worth the complexity at the moment.
+ fn failComptimeField(
+ self: @This(),
+ node: Zoir.Node.Index,
+ field: usize,
+ ) error{ OutOfMemory, ParseZon } {
+ @branchHint(.cold);
+ const ast_node = node.getAstNode(self.zoir);
+ var buf: [2]Ast.Node.Index = undefined;
+ const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: {
+ const field_node = struct_init.ast.fields[field];
+ break :b self.ast.firstToken(field_node);
+ } else b: {
+ const array_init = self.ast.fullArrayInit(&buf, ast_node).?;
+ const value_node = array_init.ast.elements[field];
+ break :b self.ast.firstToken(value_node);
+ };
+ return self.failToken(.{
+ .token = token,
+ .offset = 0,
+ .message = "cannot initialize comptime field",
+ .owned = false,
+ .note = null,
+ });
+ }
+};
+
+fn intFromFloatExact(T: type, value: anytype) ?T {
+ if (value > std.math.maxInt(T) or value < std.math.minInt(T)) {
+ return null;
+ }
+
+ if (std.math.isNan(value) or std.math.trunc(value) != value) {
+ return null;
+ }
+
+ return @intFromFloat(value);
+}
+
+fn canParseType(T: type) bool {
+ comptime return canParseTypeInner(T, &.{}, false);
+}
+
+fn canParseTypeInner(
+ T: type,
+ /// Visited structs and unions, to avoid infinite recursion.
+ /// Tracking more types is unnecessary, and a little complex due to optional nesting.
+ visited: []const type,
+ parent_is_optional: bool,
+) bool {
+ return switch (@typeInfo(T)) {
+ .bool,
+ .int,
+ .float,
+ .null,
+ .@"enum",
+ => true,
+
+ .noreturn,
+ .void,
+ .type,
+ .undefined,
+ .error_union,
+ .error_set,
+ .@"fn",
+ .frame,
+ .@"anyframe",
+ .@"opaque",
+ .comptime_int,
+ .comptime_float,
+ .enum_literal,
+ => false,
+
+ .pointer => |pointer| switch (pointer.size) {
+ .one => canParseTypeInner(pointer.child, visited, parent_is_optional),
+ .slice => canParseTypeInner(pointer.child, visited, false),
+ .many, .c => false,
+ },
+
+ .optional => |optional| if (parent_is_optional)
+ false
+ else
+ canParseTypeInner(optional.child, visited, true),
+
+ .array => |array| canParseTypeInner(array.child, visited, false),
+ .vector => |vector| canParseTypeInner(vector.child, visited, false),
+
+ .@"struct" => |@"struct"| {
+ for (visited) |V| if (T == V) return true;
+ const new_visited = visited ++ .{T};
+ for (@"struct".fields) |field| {
+ if (!field.is_comptime and !canParseTypeInner(field.type, new_visited, false)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ .@"union" => |@"union"| {
+ for (visited) |V| if (T == V) return true;
+ const new_visited = visited ++ .{T};
+ for (@"union".fields) |field| {
+ if (field.type != void and !canParseTypeInner(field.type, new_visited, false)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ };
+}
+
+test "std.zon parse canParseType" {
+ try std.testing.expect(!comptime canParseType(void));
+ try std.testing.expect(!comptime canParseType(struct { f: [*]u8 }));
+ try std.testing.expect(!comptime canParseType(struct { error{foo} }));
+ try std.testing.expect(!comptime canParseType(union(enum) { a: void, b: [*c]u8 }));
+ try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8)));
+ try std.testing.expect(!comptime canParseType(*?[*c]u8));
+ try std.testing.expect(comptime canParseType(enum(u8) { _ }));
+ try std.testing.expect(comptime canParseType(union { foo: void }));
+ try std.testing.expect(comptime canParseType(union(enum) { foo: void }));
+ try std.testing.expect(!comptime canParseType(comptime_float));
+ try std.testing.expect(!comptime canParseType(comptime_int));
+ try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null }));
+ try std.testing.expect(!comptime canParseType(@TypeOf(.foo)));
+ try std.testing.expect(comptime canParseType(?u8));
+ try std.testing.expect(comptime canParseType(*?*u8));
+ try std.testing.expect(comptime canParseType(?struct {
+ foo: ?struct {
+ ?union(enum) {
+ a: ?@Vector(0, ?*u8),
+ },
+ ?struct {
+ f: ?[]?u8,
+ },
+ },
+ }));
+ try std.testing.expect(!comptime canParseType(??u8));
+ try std.testing.expect(!comptime canParseType(?*?u8));
+ try std.testing.expect(!comptime canParseType(*?*?*u8));
+ try std.testing.expect(!comptime canParseType(struct { x: comptime_int = 2 }));
+ try std.testing.expect(!comptime canParseType(struct { x: comptime_float = 2 }));
+ try std.testing.expect(comptime canParseType(struct { comptime x: @TypeOf(.foo) = .foo }));
+ try std.testing.expect(!comptime canParseType(struct { comptime_int }));
+ const Recursive = struct { foo: ?*@This() };
+ try std.testing.expect(comptime canParseType(Recursive));
+
+ // Make sure we validate nested optional before we early out due to already having seen
+ // a type recursion!
+ try std.testing.expect(!comptime canParseType(struct {
+ add_to_visited: ?u8,
+ retrieve_from_visited: ??u8,
+ }));
+}
+
+test "std.zon requiresAllocator" {
+ try std.testing.expect(!requiresAllocator(u8));
+ try std.testing.expect(!requiresAllocator(f32));
+ try std.testing.expect(!requiresAllocator(enum { foo }));
+ try std.testing.expect(!requiresAllocator(struct { f32 }));
+ try std.testing.expect(!requiresAllocator(struct { x: f32 }));
+ try std.testing.expect(!requiresAllocator([0][]const u8));
+ try std.testing.expect(!requiresAllocator([2]u8));
+ try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 }));
+ try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 }));
+ try std.testing.expect(!requiresAllocator(?f32));
+ try std.testing.expect(!requiresAllocator(void));
+ try std.testing.expect(!requiresAllocator(@TypeOf(null)));
+ try std.testing.expect(!requiresAllocator(@Vector(3, u8)));
+ try std.testing.expect(!requiresAllocator(@Vector(0, *const u8)));
+
+ try std.testing.expect(requiresAllocator([]u8));
+ try std.testing.expect(requiresAllocator(*struct { u8, u8 }));
+ try std.testing.expect(requiresAllocator([1][]const u8));
+ try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 }));
+ try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 }));
+ try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 }));
+ try std.testing.expect(requiresAllocator(?[]u8));
+ try std.testing.expect(requiresAllocator(@Vector(3, *const u8)));
+}
+
+test "std.zon ast errors" {
+ const gpa = std.testing.allocator;
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status});
+}
+
+test "std.zon comments" {
+ const gpa = std.testing.allocator;
+
+ try std.testing.expectEqual(@as(u8, 10), fromSlice(u8, gpa,
+ \\// comment
+ \\10 // comment
+ \\// comment
+ , null, .{}));
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa,
+ \\//! comment
+ \\10 // comment
+ \\// comment
+ , &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected expression, found 'a document comment'\n",
+ "{}",
+ .{status},
+ );
+ }
+}
+
+test "std.zon failure/oom formatting" {
+ const gpa = std.testing.allocator;
+ var failing_allocator = std.testing.FailingAllocator.init(gpa, .{
+ .fail_index = 0,
+ .resize_fail_index = 0,
+ });
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.OutOfMemory, fromSlice(
+ []const u8,
+ failing_allocator.allocator(),
+ "\"foo\"",
+ &status,
+ .{},
+ ));
+ try std.testing.expectFmt("", "{}", .{status});
+}
+
+test "std.zon fromSlice syntax error" {
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(u8, std.testing.allocator, ".{", null, .{}),
+ );
+}
+
+test "std.zon optional" {
+ const gpa = std.testing.allocator;
+
+ // Basic usage
+ {
+ const none = try fromSlice(?u32, gpa, "null", null, .{});
+ try std.testing.expect(none == null);
+ const some = try fromSlice(?u32, gpa, "1", null, .{});
+ try std.testing.expect(some.? == 1);
+ }
+
+ // Deep free
+ {
+ const none = try fromSlice(?[]const u8, gpa, "null", null, .{});
+ try std.testing.expect(none == null);
+ const some = try fromSlice(?[]const u8, gpa, "\"foo\"", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqualStrings("foo", some.?);
+ }
+}
+
+test "std.zon unions" {
+ const gpa = std.testing.allocator;
+
+ // Unions
+ {
+ const Tagged = union(enum) { x: f32, @"y y": bool, z, @"z z" };
+ const Untagged = union { x: f32, @"y y": bool, z: void, @"z z": void };
+
+ const tagged_x = try fromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{});
+ try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x);
+ const tagged_y = try fromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{});
+ try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y);
+ const tagged_z_shorthand = try fromSlice(Tagged, gpa, ".z", null, .{});
+ try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand);
+ const tagged_zz_shorthand = try fromSlice(Tagged, gpa, ".@\"z z\"", null, .{});
+ try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand);
+
+ const untagged_x = try fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{});
+ try std.testing.expect(untagged_x.x == 1.5);
+ const untagged_y = try fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{});
+ try std.testing.expect(untagged_y.@"y y");
+ }
+
+ // Deep free
+ {
+ const Union = union(enum) { bar: []const u8, baz: bool };
+
+ const noalloc = try fromSlice(Union, gpa, ".{.baz = false}", null, .{});
+ try std.testing.expectEqual(Union{ .baz = false }, noalloc);
+
+ const alloc = try fromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{});
+ defer free(gpa, alloc);
+ try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc);
+ }
+
+ // Unknown field
+ {
+ const Union = union { x: f32, y: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Union, gpa, ".{.z=2.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:4: error: unexpected field 'z'
+ \\1:4: note: supported: 'x', 'y'
+ \\
+ ,
+ "{}",
+ .{status},
+ );
+ }
+
+ // Explicit void field
+ {
+ const Union = union(enum) { x: void };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Union, gpa, ".{.x=1}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status});
+ }
+
+ // Extra field
+ {
+ const Union = union { x: f32, y: bool };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status});
+ }
+
+ // No fields
+ {
+ const Union = union { x: f32, y: bool };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Union, gpa, ".{}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status});
+ }
+
+ // Enum literals cannot coerce into untagged unions
+ {
+ const Union = union { x: void };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{}));
+ try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status});
+ }
+
+ // Unknown field for enum literal coercion
+ {
+ const Union = union(enum) { x: void };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".y", &status, .{}));
+ try std.testing.expectFmt(
+ \\1:2: error: unexpected field 'y'
+ \\1:2: note: supported: 'x'
+ \\
+ ,
+ "{}",
+ .{status},
+ );
+ }
+
+ // Non void field for enum literal coercion
+ {
+ const Union = union(enum) { x: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{}));
+ try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status});
+ }
+}
+
+test "std.zon structs" {
+ const gpa = std.testing.allocator;
+
+ // Structs (various sizes tested since they're parsed differently)
+ {
+ const Vec0 = struct {};
+ const Vec1 = struct { x: f32 };
+ const Vec2 = struct { x: f32, y: f32 };
+ const Vec3 = struct { x: f32, y: f32, z: f32 };
+
+ const zero = try fromSlice(Vec0, gpa, ".{}", null, .{});
+ try std.testing.expectEqual(Vec0{}, zero);
+
+ const one = try fromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{});
+ try std.testing.expectEqual(Vec1{ .x = 1.2 }, one);
+
+ const two = try fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{});
+ try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two);
+
+ const three = try fromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{});
+ try std.testing.expectEqual(Vec3{ .x = 1.2, .y = 3.4, .z = 5.6 }, three);
+ }
+
+ // Deep free (structs and arrays)
+ {
+ const Foo = struct { bar: []const u8, baz: []const []const u8 };
+
+ const parsed = try fromSlice(
+ Foo,
+ gpa,
+ ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}",
+ null,
+ .{},
+ );
+ defer free(gpa, parsed);
+ try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed);
+ }
+
+ // Unknown field
+ {
+ const Vec2 = struct { x: f32, y: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:12: error: unexpected field 'z'
+ \\1:12: note: supported: 'x', 'y'
+ \\
+ ,
+ "{}",
+ .{status},
+ );
+ }
+
+ // Duplicate field
+ {
+ const Vec2 = struct { x: f32, y: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5, .x=3.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:4: error: duplicate struct field name
+ \\1:12: note: duplicate name here
+ \\
+ , "{}", .{status});
+ }
+
+ // Ignore unknown fields
+ {
+ const Vec2 = struct { x: f32, y: f32 = 2.0 };
+ const parsed = try fromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", null, .{
+ .ignore_unknown_fields = true,
+ });
+ try std.testing.expectEqual(Vec2{ .x = 1.0, .y = 2.0 }, parsed);
+ }
+
+ // Unknown field when struct has no fields (regression test)
+ {
+ const Vec2 = struct {};
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:4: error: unexpected field 'x'
+ \\1:4: note: none expected
+ \\
+ , "{}", .{status});
+ }
+
+ // Missing field
+ {
+ const Vec2 = struct { x: f32, y: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status});
+ }
+
+ // Default field
+ {
+ const Vec2 = struct { x: f32, y: f32 = 1.5 };
+ const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{});
+ try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed);
+ }
+
+ // Comptime field
+ {
+ const Vec2 = struct { x: f32, comptime y: f32 = 1.5 };
+ const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{});
+ try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed);
+ }
+
+ // Comptime field assignment
+ {
+ const Vec2 = struct { x: f32, comptime y: f32 = 1.5 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:18: error: cannot initialize comptime field
+ \\
+ , "{}", .{status});
+ }
+
+ // Enum field (regression test, we were previously getting the field name in an
+ // incorrect way that broke for enum values)
+ {
+ const Vec0 = struct { x: enum { x } };
+ const parsed = try fromSlice(Vec0, gpa, ".{ .x = .x }", null, .{});
+ try std.testing.expectEqual(Vec0{ .x = .x }, parsed);
+ }
+
+ // Enum field and struct field with @
+ {
+ const Vec0 = struct { @"x x": enum { @"x x" } };
+ const parsed = try fromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{});
+ try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed);
+ }
+
+ // Type expressions are not allowed
+ {
+ // Structs
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice(struct {}, gpa, "Empty{}", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:1: error: types are not available in ZON
+ \\1:1: note: replace the type with '.'
+ \\
+ , "{}", .{status});
+ }
+
+ // Arrays
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice([3]u8, gpa, "[3]u8{1, 2, 3}", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:1: error: types are not available in ZON
+ \\1:1: note: replace the type with '.'
+ \\
+ , "{}", .{status});
+ }
+
+ // Slices
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice([]u8, gpa, "[]u8{1, 2, 3}", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:1: error: types are not available in ZON
+ \\1:1: note: replace the type with '.'
+ \\
+ , "{}", .{status});
+ }
+
+ // Tuples
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice(
+ struct { u8, u8, u8 },
+ gpa,
+ "Tuple{1, 2, 3}",
+ &status,
+ .{},
+ );
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:1: error: types are not available in ZON
+ \\1:1: note: replace the type with '.'
+ \\
+ , "{}", .{status});
+ }
+
+ // Nested
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice(struct {}, gpa, ".{ .x = Tuple{1, 2, 3} }", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:9: error: types are not available in ZON
+ \\1:9: note: replace the type with '.'
+ \\
+ , "{}", .{status});
+ }
+ }
+}
+
+test "std.zon tuples" {
+ const gpa = std.testing.allocator;
+
+ // Structs (various sizes tested since they're parsed differently)
+ {
+ const Tuple0 = struct {};
+ const Tuple1 = struct { f32 };
+ const Tuple2 = struct { f32, bool };
+ const Tuple3 = struct { f32, bool, u8 };
+
+ const zero = try fromSlice(Tuple0, gpa, ".{}", null, .{});
+ try std.testing.expectEqual(Tuple0{}, zero);
+
+ const one = try fromSlice(Tuple1, gpa, ".{1.2}", null, .{});
+ try std.testing.expectEqual(Tuple1{1.2}, one);
+
+ const two = try fromSlice(Tuple2, gpa, ".{1.2, true}", null, .{});
+ try std.testing.expectEqual(Tuple2{ 1.2, true }, two);
+
+ const three = try fromSlice(Tuple3, gpa, ".{1.2, false, 3}", null, .{});
+ try std.testing.expectEqual(Tuple3{ 1.2, false, 3 }, three);
+ }
+
+ // Deep free
+ {
+ const Tuple = struct { []const u8, []const u8 };
+ const parsed = try fromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{});
+ defer free(gpa, parsed);
+ try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed);
+ }
+
+ // Extra field
+ {
+ const Tuple = struct { f32, bool };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status});
+ }
+
+ // Extra field
+ {
+ const Tuple = struct { f32, bool };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Tuple, gpa, ".{0.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: missing tuple field with index 1\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Tuple with unexpected field names
+ {
+ const Tuple = struct { f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status});
+ }
+
+ // Struct with missing field names
+ {
+ const Struct = struct { foo: f32 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Struct, gpa, ".{10.0}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status});
+ }
+
+ // Comptime field
+ {
+ const Vec2 = struct { f32, comptime f32 = 1.5 };
+ const parsed = try fromSlice(Vec2, gpa, ".{ 1.2 }", null, .{});
+ try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed);
+ }
+
+ // Comptime field assignment
+ {
+ const Vec2 = struct { f32, comptime f32 = 1.5 };
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ const parsed = fromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{});
+ try std.testing.expectError(error.ParseZon, parsed);
+ try std.testing.expectFmt(
+ \\1:9: error: cannot initialize comptime field
+ \\
+ , "{}", .{status});
+ }
+}
+
+// Test sizes 0 to 3 since small sizes get parsed differently
+test "std.zon arrays and slices" {
+ if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/20881
+
+ const gpa = std.testing.allocator;
+
+ // Literals
+ {
+ // Arrays
+ {
+ const zero = try fromSlice([0]u8, gpa, ".{}", null, .{});
+ try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero);
+
+ const one = try fromSlice([1]u8, gpa, ".{'a'}", null, .{});
+ try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one);
+
+ const two = try fromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{});
+ try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two);
+
+ const two_comma = try fromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{});
+ try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma);
+
+ const three = try fromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{});
+ try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three);
+
+ const sentinel = try fromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", null, .{});
+ const expected_sentinel: [3:'z']u8 = .{ 'a', 'b', 'c' };
+ try std.testing.expectEqualSlices(u8, &expected_sentinel, &sentinel);
+ }
+
+ // Slice literals
+ {
+ const zero = try fromSlice([]const u8, gpa, ".{}", null, .{});
+ defer free(gpa, zero);
+ try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero);
+
+ const one = try fromSlice([]u8, gpa, ".{'a'}", null, .{});
+ defer free(gpa, one);
+ try std.testing.expectEqualSlices(u8, &.{'a'}, one);
+
+ const two = try fromSlice([]const u8, gpa, ".{'a', 'b'}", null, .{});
+ defer free(gpa, two);
+ try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two);
+
+ const two_comma = try fromSlice([]const u8, gpa, ".{'a', 'b',}", null, .{});
+ defer free(gpa, two_comma);
+ try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma);
+
+ const three = try fromSlice([]u8, gpa, ".{'a', 'b', 'c'}", null, .{});
+ defer free(gpa, three);
+ try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three);
+
+ const sentinel = try fromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{});
+ defer free(gpa, sentinel);
+ const expected_sentinel: [:'z']const u8 = &.{ 'a', 'b', 'c' };
+ try std.testing.expectEqualSlices(u8, expected_sentinel, sentinel);
+ }
+ }
+
+ // Deep free
+ {
+ // Arrays
+ {
+ const parsed = try fromSlice([1][]const u8, gpa, ".{\"abc\"}", null, .{});
+ defer free(gpa, parsed);
+ const expected: [1][]const u8 = .{"abc"};
+ try std.testing.expectEqualDeep(expected, parsed);
+ }
+
+ // Slice literals
+ {
+ const parsed = try fromSlice([]const []const u8, gpa, ".{\"abc\"}", null, .{});
+ defer free(gpa, parsed);
+ const expected: []const []const u8 = &.{"abc"};
+ try std.testing.expectEqualDeep(expected, parsed);
+ }
+ }
+
+ // Sentinels and alignment
+ {
+ // Arrays
+ {
+ const sentinel = try fromSlice([1:2]u8, gpa, ".{1}", null, .{});
+ try std.testing.expectEqual(@as(usize, 1), sentinel.len);
+ try std.testing.expectEqual(@as(u8, 1), sentinel[0]);
+ try std.testing.expectEqual(@as(u8, 2), sentinel[1]);
+ }
+
+ // Slice literals
+ {
+ const sentinel = try fromSlice([:2]align(4) u8, gpa, ".{1}", null, .{});
+ defer free(gpa, sentinel);
+ try std.testing.expectEqual(@as(usize, 1), sentinel.len);
+ try std.testing.expectEqual(@as(u8, 1), sentinel[0]);
+ try std.testing.expectEqual(@as(u8, 2), sentinel[1]);
+ }
+ }
+
+ // Expect 0 find 3
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:3: error: index 0 outside of array of length 0\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Expect 1 find 2
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:8: error: index 1 outside of array of length 1\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Expect 2 find 1
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([2]u8, gpa, ".{'a'}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: expected 2 array elements; found 1\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Expect 3 find 0
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([3]u8, gpa, ".{}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: expected 3 array elements; found 0\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Wrong inner type
+ {
+ // Array
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status});
+ }
+
+ // Slice
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status});
+ }
+ }
+
+ // Complete wrong type
+ {
+ // Array
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([3]u8, gpa, "'a'", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ // Slice
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]u8, gpa, "'a'", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Address of is not allowed (indirection for slices in ZON is implicit)
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:3: error: pointers are not available in ZON\n",
+ "{}",
+ .{status},
+ );
+ }
+}
+
+test "std.zon string literal" {
+ const gpa = std.testing.allocator;
+
+ // Basic string literal
+ {
+ const parsed = try fromSlice([]const u8, gpa, "\"abc\"", null, .{});
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed);
+ }
+
+ // String literal with escape characters
+ {
+ const parsed = try fromSlice([]const u8, gpa, "\"ab\\nc\"", null, .{});
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed);
+ }
+
+ // String literal with embedded null
+ {
+ const parsed = try fromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{});
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed);
+ }
+
+ // Passing string literal to a mutable slice
+ {
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]u8, gpa, "\"abcd\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]u8, gpa, "\\\\abcd", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Passing string literal to a array
+ {
+ {
+ var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon);
+ defer ast.deinit(gpa);
+ var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
+ defer zoir.deinit(gpa);
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Zero terminated slices
+ {
+ {
+ const parsed: [:0]const u8 = try fromSlice(
+ [:0]const u8,
+ gpa,
+ "\"abc\"",
+ null,
+ .{},
+ );
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings("abc", parsed);
+ try std.testing.expectEqual(@as(u8, 0), parsed[3]);
+ }
+
+ {
+ const parsed: [:0]const u8 = try fromSlice(
+ [:0]const u8,
+ gpa,
+ "\\\\abc",
+ null,
+ .{},
+ );
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings("abc", parsed);
+ try std.testing.expectEqual(@as(u8, 0), parsed[3]);
+ }
+ }
+
+ // Other value terminated slices
+ {
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Expecting string literal, getting something else
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]const u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected string\n", "{}", .{status});
+ }
+
+ // Expecting string literal, getting an incompatible tuple
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]const u8, gpa, ".{false}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{}", .{status});
+ }
+
+ // Invalid string literal
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]const i8, gpa, "\"\\a\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{}", .{status});
+ }
+
+ // Slice wrong child type
+ {
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]const i8, gpa, "\"a\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]const i8, gpa, "\\\\a", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Bad alignment
+ {
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status});
+ }
+ }
+
+ // Multi line strings
+ inline for (.{ []const u8, [:0]const u8 }) |String| {
+ // Nested
+ {
+ const S = struct {
+ message: String,
+ message2: String,
+ message3: String,
+ };
+ const parsed = try fromSlice(S, gpa,
+ \\.{
+ \\ .message =
+ \\ \\hello, world!
+ \\
+ \\ \\this is a multiline string!
+ \\ \\
+ \\ \\...
+ \\
+ \\ ,
+ \\ .message2 =
+ \\ \\this too...sort of.
+ \\ ,
+ \\ .message3 =
+ \\ \\
+ \\ \\and this.
+ \\}
+ , null, .{});
+ defer free(gpa, parsed);
+ try std.testing.expectEqualStrings(
+ "hello, world!\nthis is a multiline string!\n\n...",
+ parsed.message,
+ );
+ try std.testing.expectEqualStrings("this too...sort of.", parsed.message2);
+ try std.testing.expectEqualStrings("\nand this.", parsed.message3);
+ }
+ }
+}
+
+test "std.zon enum literals" {
+ const gpa = std.testing.allocator;
+
+ const Enum = enum {
+ foo,
+ bar,
+ baz,
+ @"ab\nc",
+ };
+
+ // Tags that exist
+ try std.testing.expectEqual(Enum.foo, try fromSlice(Enum, gpa, ".foo", null, .{}));
+ try std.testing.expectEqual(Enum.bar, try fromSlice(Enum, gpa, ".bar", null, .{}));
+ try std.testing.expectEqual(Enum.baz, try fromSlice(Enum, gpa, ".baz", null, .{}));
+ try std.testing.expectEqual(
+ Enum.@"ab\nc",
+ try fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}),
+ );
+
+ // Bad tag
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Enum, gpa, ".qux", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:2: error: unexpected enum literal 'qux'
+ \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"'
+ \\
+ ,
+ "{}",
+ .{status},
+ );
+ }
+
+ // Bad tag that's too long for parser
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:2: error: unexpected enum literal 'foobarbaz'
+ \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"'
+ \\
+ ,
+ "{}",
+ .{status},
+ );
+ }
+
+ // Bad type
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Enum, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status});
+ }
+
+ // Test embedded nulls in an identifier
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: identifier cannot contain null bytes\n",
+ "{}",
+ .{status},
+ );
+ }
+}
+
+test "std.zon parse bool" {
+ const gpa = std.testing.allocator;
+
+ // Correct floats
+ try std.testing.expectEqual(true, try fromSlice(bool, gpa, "true", null, .{}));
+ try std.testing.expectEqual(false, try fromSlice(bool, gpa, "false", null, .{}));
+
+ // Errors
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(bool, gpa, " foo", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ \\1:2: error: invalid expression
+ \\1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'
+ \\1:2: note: precede identifier with '.' for an enum literal
+ \\
+ , "{}", .{status});
+ }
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(bool, gpa, "123", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{}", .{status});
+ }
+}
+
+test "std.zon intFromFloatExact" {
+ // Valid conversions
+ try std.testing.expectEqual(@as(u8, 10), intFromFloatExact(u8, @as(f32, 10.0)).?);
+ try std.testing.expectEqual(@as(i8, -123), intFromFloatExact(i8, @as(f64, @as(f64, -123.0))).?);
+ try std.testing.expectEqual(@as(i16, 45), intFromFloatExact(i16, @as(f128, @as(f128, 45.0))).?);
+
+ // Out of range
+ try std.testing.expectEqual(@as(?u4, null), intFromFloatExact(u4, @as(f32, 16.0)));
+ try std.testing.expectEqual(@as(?i4, null), intFromFloatExact(i4, @as(f64, -17.0)));
+ try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f128, -2.0)));
+
+ // Not a whole number
+ try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f32, 0.5)));
+ try std.testing.expectEqual(@as(?i8, null), intFromFloatExact(i8, @as(f64, 0.01)));
+
+ // Infinity and NaN
+ try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.inf(f32)));
+ try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, -std.math.inf(f32)));
+ try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.nan(f32)));
+}
+
+test "std.zon parse int" {
+ const gpa = std.testing.allocator;
+
+ // Test various numbers and types
+ try std.testing.expectEqual(@as(u8, 10), try fromSlice(u8, gpa, "10", null, .{}));
+ try std.testing.expectEqual(@as(i16, 24), try fromSlice(i16, gpa, "24", null, .{}));
+ try std.testing.expectEqual(@as(i14, -4), try fromSlice(i14, gpa, "-4", null, .{}));
+ try std.testing.expectEqual(@as(i32, -123), try fromSlice(i32, gpa, "-123", null, .{}));
+
+ // Test limits
+ try std.testing.expectEqual(@as(i8, 127), try fromSlice(i8, gpa, "127", null, .{}));
+ try std.testing.expectEqual(@as(i8, -128), try fromSlice(i8, gpa, "-128", null, .{}));
+
+ // Test characters
+ try std.testing.expectEqual(@as(u8, 'a'), try fromSlice(u8, gpa, "'a'", null, .{}));
+ try std.testing.expectEqual(@as(u8, 'z'), try fromSlice(u8, gpa, "'z'", null, .{}));
+
+ // Test big integers
+ try std.testing.expectEqual(
+ @as(u65, 36893488147419103231),
+ try fromSlice(u65, gpa, "36893488147419103231", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(u65, 36893488147419103231),
+ try fromSlice(u65, gpa, "368934_881_474191032_31", null, .{}),
+ );
+
+ // Test big integer limits
+ try std.testing.expectEqual(
+ @as(i66, 36893488147419103231),
+ try fromSlice(i66, gpa, "36893488147419103231", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(i66, -36893488147419103232),
+ try fromSlice(i66, gpa, "-36893488147419103232", null, .{}),
+ );
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(
+ i66,
+ gpa,
+ "36893488147419103232",
+ &status,
+ .{},
+ ));
+ try std.testing.expectFmt(
+ "1:1: error: type 'i66' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(
+ i66,
+ gpa,
+ "-36893488147419103233",
+ &status,
+ .{},
+ ));
+ try std.testing.expectFmt(
+ "1:1: error: type 'i66' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Test parsing whole number floats as integers
+ try std.testing.expectEqual(@as(i8, -1), try fromSlice(i8, gpa, "-1.0", null, .{}));
+ try std.testing.expectEqual(@as(i8, 123), try fromSlice(i8, gpa, "123.0", null, .{}));
+
+ // Test non-decimal integers
+ try std.testing.expectEqual(@as(i16, 0xff), try fromSlice(i16, gpa, "0xff", null, .{}));
+ try std.testing.expectEqual(@as(i16, -0xff), try fromSlice(i16, gpa, "-0xff", null, .{}));
+ try std.testing.expectEqual(@as(i16, 0o77), try fromSlice(i16, gpa, "0o77", null, .{}));
+ try std.testing.expectEqual(@as(i16, -0o77), try fromSlice(i16, gpa, "-0o77", null, .{}));
+ try std.testing.expectEqual(@as(i16, 0b11), try fromSlice(i16, gpa, "0b11", null, .{}));
+ try std.testing.expectEqual(@as(i16, -0b11), try fromSlice(i16, gpa, "-0b11", null, .{}));
+
+ // Test non-decimal big integers
+ try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice(
+ u65,
+ gpa,
+ "0x1ffffffffffffffff",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "0x1ffffffffffffffff",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "-0x1ffffffffffffffff",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice(
+ u65,
+ gpa,
+ "0o3777777777777777777777",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "0o3777777777777777777777",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "-0o3777777777777777777777",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice(
+ u65,
+ gpa,
+ "0b11111111111111111111111111111111111111111111111111111111111111111",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "0b11111111111111111111111111111111111111111111111111111111111111111",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice(
+ i66,
+ gpa,
+ "-0b11111111111111111111111111111111111111111111111111111111111111111",
+ null,
+ .{},
+ ));
+
+ // Number with invalid character in the middle
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "32a32", &status, .{}));
+ try std.testing.expectFmt(
+ "1:3: error: invalid digit 'a' for decimal base\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Failing to parse as int
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "true", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{}", .{status});
+ }
+
+ // Failing because an int is out of range
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "256", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: type 'u8' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Failing because a negative int is out of range
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-129", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: type 'i8' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Failing because an unsigned int is negative
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: type 'u8' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Failing because a float is non-whole
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "1.5", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: type 'u8' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Failing because a float is negative
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1.0", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: type 'u8' cannot represent value\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Negative integer zero
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-0", &status, .{}));
+ try std.testing.expectFmt(
+ \\1:2: error: integer literal '-0' is ambiguous
+ \\1:2: note: use '0' for an integer zero
+ \\1:2: note: use '-0.0' for a floating-point signed zero
+ \\
+ , "{}", .{status});
+ }
+
+ // Negative integer zero casted to float
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-0", &status, .{}));
+ try std.testing.expectFmt(
+ \\1:2: error: integer literal '-0' is ambiguous
+ \\1:2: note: use '0' for an integer zero
+ \\1:2: note: use '-0.0' for a floating-point signed zero
+ \\
+ , "{}", .{status});
+ }
+
+ // Negative float 0 is allowed
+ try std.testing.expect(
+ std.math.isNegativeZero(try fromSlice(f32, gpa, "-0.0", null, .{})),
+ );
+ try std.testing.expect(std.math.isPositiveZero(try fromSlice(f32, gpa, "0.0", null, .{})));
+
+ // Double negation is not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "--2", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(f32, gpa, "--2.0", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Invalid int literal
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0xg", &status, .{}));
+ try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base\n", "{}", .{status});
+ }
+
+ // Notes on invalid int literal
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0123", &status, .{}));
+ try std.testing.expectFmt(
+ \\1:1: error: number '0123' has leading zero
+ \\1:1: note: use '0o' prefix for octal literals
+ \\
+ , "{}", .{status});
+ }
+}
+
+test "std.zon negative char" {
+ const gpa = std.testing.allocator;
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-'a'", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i16, gpa, "-'a'", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+}
+
+test "std.zon parse float" {
+ const gpa = std.testing.allocator;
+
+ // Test decimals
+ try std.testing.expectEqual(@as(f16, 0.5), try fromSlice(f16, gpa, "0.5", null, .{}));
+ try std.testing.expectEqual(
+ @as(f32, 123.456),
+ try fromSlice(f32, gpa, "123.456", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(f64, -123.456),
+ try fromSlice(f64, gpa, "-123.456", null, .{}),
+ );
+ try std.testing.expectEqual(@as(f128, 42.5), try fromSlice(f128, gpa, "42.5", null, .{}));
+
+ // Test whole numbers with and without decimals
+ try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5.0", null, .{}));
+ try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5", null, .{}));
+ try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102.0", null, .{}));
+ try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102", null, .{}));
+
+ // Test characters and negated characters
+ try std.testing.expectEqual(@as(f32, 'a'), try fromSlice(f32, gpa, "'a'", null, .{}));
+ try std.testing.expectEqual(@as(f32, 'z'), try fromSlice(f32, gpa, "'z'", null, .{}));
+
+ // Test big integers
+ try std.testing.expectEqual(
+ @as(f32, 36893488147419103231),
+ try fromSlice(f32, gpa, "36893488147419103231", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(f32, -36893488147419103231),
+ try fromSlice(f32, gpa, "-36893488147419103231", null, .{}),
+ );
+ try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice(
+ f128,
+ gpa,
+ "0x1ffffffffffffffff",
+ null,
+ .{},
+ ));
+ try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice(
+ f32,
+ gpa,
+ "0x1ffffffffffffffff",
+ null,
+ .{},
+ ));
+
+ // Exponents, underscores
+ try std.testing.expectEqual(
+ @as(f32, 123.0E+77),
+ try fromSlice(f32, gpa, "12_3.0E+77", null, .{}),
+ );
+
+ // Hexadecimal
+ try std.testing.expectEqual(
+ @as(f32, 0x103.70p-5),
+ try fromSlice(f32, gpa, "0x103.70p-5", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(f32, -0x103.70),
+ try fromSlice(f32, gpa, "-0x103.70", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @as(f32, 0x1234_5678.9ABC_CDEFp-10),
+ try fromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}),
+ );
+
+ // inf, nan
+ try std.testing.expect(std.math.isPositiveInf(try fromSlice(f32, gpa, "inf", null, .{})));
+ try std.testing.expect(std.math.isNegativeInf(try fromSlice(f32, gpa, "-inf", null, .{})));
+ try std.testing.expect(std.math.isNan(try fromSlice(f32, gpa, "nan", null, .{})));
+
+ // Negative nan not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-nan", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // nan as int not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status});
+ }
+
+ // nan as int not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status});
+ }
+
+ // inf as int not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "inf", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status});
+ }
+
+ // -inf as int not allowed
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-inf", &status, .{}));
+ try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status});
+ }
+
+ // Bad identifier as float
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "foo", &status, .{}));
+ try std.testing.expectFmt(
+ \\1:1: error: invalid expression
+ \\1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'
+ \\1:1: note: precede identifier with '.' for an enum literal
+ \\
+ , "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-foo", &status, .{}));
+ try std.testing.expectFmt(
+ "1:1: error: expected number or 'inf' after '-'\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Non float as float
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(f32, gpa, "\"foo\"", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status});
+ }
+}
+
+test "std.zon free on error" {
+ // Test freeing partially allocated structs
+ {
+ const Struct = struct {
+ x: []const u8,
+ y: []const u8,
+ z: bool,
+ };
+ try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator,
+ \\.{
+ \\ .x = "hello",
+ \\ .y = "world",
+ \\ .z = "fail",
+ \\}
+ , null, .{}));
+ }
+
+ // Test freeing partially allocated tuples
+ {
+ const Struct = struct {
+ []const u8,
+ []const u8,
+ bool,
+ };
+ try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator,
+ \\.{
+ \\ "hello",
+ \\ "world",
+ \\ "fail",
+ \\}
+ , null, .{}));
+ }
+
+ // Test freeing structs with missing fields
+ {
+ const Struct = struct {
+ x: []const u8,
+ y: bool,
+ };
+ try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator,
+ \\.{
+ \\ .x = "hello",
+ \\}
+ , null, .{}));
+ }
+
+ // Test freeing partially allocated arrays
+ {
+ try std.testing.expectError(error.ParseZon, fromSlice(
+ [3][]const u8,
+ std.testing.allocator,
+ \\.{
+ \\ "hello",
+ \\ false,
+ \\ false,
+ \\}
+ ,
+ null,
+ .{},
+ ));
+ }
+
+ // Test freeing partially allocated slices
+ {
+ try std.testing.expectError(error.ParseZon, fromSlice(
+ [][]const u8,
+ std.testing.allocator,
+ \\.{
+ \\ "hello",
+ \\ "world",
+ \\ false,
+ \\}
+ ,
+ null,
+ .{},
+ ));
+ }
+
+ // We can parse types that can't be freed, as long as they contain no allocations, e.g. untagged
+ // unions.
+ try std.testing.expectEqual(
+ @as(f32, 1.5),
+ (try fromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", null, .{})).x,
+ );
+
+ // We can also parse types that can't be freed if it's impossible for an error to occur after
+ // the allocation, as is the case here.
+ {
+ const result = try fromSlice(
+ union { x: []const u8 },
+ std.testing.allocator,
+ ".{ .x = \"foo\" }",
+ null,
+ .{},
+ );
+ defer free(std.testing.allocator, result.x);
+ try std.testing.expectEqualStrings("foo", result.x);
+ }
+
+ // However, if it's possible we could get an error requiring we free the value, but the value
+ // cannot be freed (e.g. untagged unions) then we need to turn off `free_on_error` for it to
+ // compile.
+ {
+ const S = struct {
+ union { x: []const u8 },
+ bool,
+ };
+ const result = try fromSlice(
+ S,
+ std.testing.allocator,
+ ".{ .{ .x = \"foo\" }, true }",
+ null,
+ .{ .free_on_error = false },
+ );
+ defer free(std.testing.allocator, result[0].x);
+ try std.testing.expectEqualStrings("foo", result[0].x);
+ try std.testing.expect(result[1]);
+ }
+
+ // Again but for structs.
+ {
+ const S = struct {
+ a: union { x: []const u8 },
+ b: bool,
+ };
+ const result = try fromSlice(
+ S,
+ std.testing.allocator,
+ ".{ .a = .{ .x = \"foo\" }, .b = true }",
+ null,
+ .{
+ .free_on_error = false,
+ },
+ );
+ defer free(std.testing.allocator, result.a.x);
+ try std.testing.expectEqualStrings("foo", result.a.x);
+ try std.testing.expect(result.b);
+ }
+
+ // Again but for arrays.
+ {
+ const S = [2]union { x: []const u8 };
+ const result = try fromSlice(
+ S,
+ std.testing.allocator,
+ ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }",
+ null,
+ .{
+ .free_on_error = false,
+ },
+ );
+ defer free(std.testing.allocator, result[0].x);
+ defer free(std.testing.allocator, result[1].x);
+ try std.testing.expectEqualStrings("foo", result[0].x);
+ try std.testing.expectEqualStrings("bar", result[1].x);
+ }
+
+ // Again but for slices.
+ {
+ const S = []union { x: []const u8 };
+ const result = try fromSlice(
+ S,
+ std.testing.allocator,
+ ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }",
+ null,
+ .{
+ .free_on_error = false,
+ },
+ );
+ defer std.testing.allocator.free(result);
+ defer free(std.testing.allocator, result[0].x);
+ defer free(std.testing.allocator, result[1].x);
+ try std.testing.expectEqualStrings("foo", result[0].x);
+ try std.testing.expectEqualStrings("bar", result[1].x);
+ }
+}
+
+test "std.zon vector" {
+ if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330
+ if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15329
+
+ const gpa = std.testing.allocator;
+
+ // Passing cases
+ try std.testing.expectEqual(
+ @Vector(0, bool){},
+ try fromSlice(@Vector(0, bool), gpa, ".{}", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @Vector(3, bool){ true, false, true },
+ try fromSlice(@Vector(3, bool), gpa, ".{true, false, true}", null, .{}),
+ );
+
+ try std.testing.expectEqual(
+ @Vector(0, f32){},
+ try fromSlice(@Vector(0, f32), gpa, ".{}", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @Vector(3, f32){ 1.5, 2.5, 3.5 },
+ try fromSlice(@Vector(3, f32), gpa, ".{1.5, 2.5, 3.5}", null, .{}),
+ );
+
+ try std.testing.expectEqual(
+ @Vector(0, u8){},
+ try fromSlice(@Vector(0, u8), gpa, ".{}", null, .{}),
+ );
+ try std.testing.expectEqual(
+ @Vector(3, u8){ 2, 4, 6 },
+ try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}),
+ );
+
+ {
+ try std.testing.expectEqual(
+ @Vector(0, *const u8){},
+ try fromSlice(@Vector(0, *const u8), gpa, ".{}", null, .{}),
+ );
+ const pointers = try fromSlice(@Vector(3, *const u8), gpa, ".{2, 4, 6}", null, .{});
+ defer free(gpa, pointers);
+ try std.testing.expectEqualDeep(@Vector(3, *const u8){ &2, &4, &6 }, pointers);
+ }
+
+ {
+ try std.testing.expectEqual(
+ @Vector(0, ?*const u8){},
+ try fromSlice(@Vector(0, ?*const u8), gpa, ".{}", null, .{}),
+ );
+ const pointers = try fromSlice(@Vector(3, ?*const u8), gpa, ".{2, null, 6}", null, .{});
+ defer free(gpa, pointers);
+ try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers);
+ }
+
+ // Too few fields
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(@Vector(2, f32), gpa, ".{0.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: expected 2 vector elements; found 1\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Too many fields
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(@Vector(2, f32), gpa, ".{0.5, 1.5, 2.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:2: error: expected 2 vector elements; found 3\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Wrong type fields
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(@Vector(3, f32), gpa, ".{0.5, true, 2.5}", &status, .{}),
+ );
+ try std.testing.expectFmt(
+ "1:8: error: expected type 'f32'\n",
+ "{}",
+ .{status},
+ );
+ }
+
+ // Wrong type
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(@Vector(3, u8), gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{}", .{status});
+ }
+
+ // Elements should get freed on error
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(@Vector(3, *u8), gpa, ".{1, true, 3}", &status, .{}),
+ );
+ try std.testing.expectFmt("1:6: error: expected type 'u8'\n", "{}", .{status});
+ }
+}
+
+test "std.zon add pointers" {
+ const gpa = std.testing.allocator;
+
+ // Primitive with varying levels of pointers
+ {
+ const result = try fromSlice(*u32, gpa, "10", null, .{});
+ defer free(gpa, result);
+ try std.testing.expectEqual(@as(u32, 10), result.*);
+ }
+
+ {
+ const result = try fromSlice(**u32, gpa, "10", null, .{});
+ defer free(gpa, result);
+ try std.testing.expectEqual(@as(u32, 10), result.*.*);
+ }
+
+ {
+ const result = try fromSlice(***u32, gpa, "10", null, .{});
+ defer free(gpa, result);
+ try std.testing.expectEqual(@as(u32, 10), result.*.*.*);
+ }
+
+ // Primitive optional with varying levels of pointers
+ {
+ const some = try fromSlice(?*u32, gpa, "10", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqual(@as(u32, 10), some.?.*);
+
+ const none = try fromSlice(?*u32, gpa, "null", null, .{});
+ defer free(gpa, none);
+ try std.testing.expectEqual(null, none);
+ }
+
+ {
+ const some = try fromSlice(*?u32, gpa, "10", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqual(@as(u32, 10), some.*.?);
+
+ const none = try fromSlice(*?u32, gpa, "null", null, .{});
+ defer free(gpa, none);
+ try std.testing.expectEqual(null, none.*);
+ }
+
+ {
+ const some = try fromSlice(?**u32, gpa, "10", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqual(@as(u32, 10), some.?.*.*);
+
+ const none = try fromSlice(?**u32, gpa, "null", null, .{});
+ defer free(gpa, none);
+ try std.testing.expectEqual(null, none);
+ }
+
+ {
+ const some = try fromSlice(*?*u32, gpa, "10", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqual(@as(u32, 10), some.*.?.*);
+
+ const none = try fromSlice(*?*u32, gpa, "null", null, .{});
+ defer free(gpa, none);
+ try std.testing.expectEqual(null, none.*);
+ }
+
+ {
+ const some = try fromSlice(**?u32, gpa, "10", null, .{});
+ defer free(gpa, some);
+ try std.testing.expectEqual(@as(u32, 10), some.*.*.?);
+
+ const none = try fromSlice(**?u32, gpa, "null", null, .{});
+ defer free(gpa, none);
+ try std.testing.expectEqual(null, none.*.*);
+ }
+
+ // Pointer to an array
+ {
+ const result = try fromSlice(*[3]u8, gpa, ".{ 1, 2, 3 }", null, .{});
+ defer free(gpa, result);
+ try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*);
+ }
+
+ // A complicated type with nested internal pointers and string allocations
+ {
+ const Inner = struct {
+ f1: *const ?*const []const u8,
+ f2: *const ?*const []const u8,
+ };
+ const Outer = struct {
+ f1: *const ?*const Inner,
+ f2: *const ?*const Inner,
+ };
+ const expected: Outer = .{
+ .f1 = &&.{
+ .f1 = &null,
+ .f2 = &&"foo",
+ },
+ .f2 = &null,
+ };
+
+ const found = try fromSlice(?*Outer, gpa,
+ \\.{
+ \\ .f1 = .{
+ \\ .f1 = null,
+ \\ .f2 = "foo",
+ \\ },
+ \\ .f2 = null,
+ \\}
+ , null, .{});
+ defer free(gpa, found);
+
+ try std.testing.expectEqualDeep(expected, found.?.*);
+ }
+
+ // Test that optional types are flattened correctly in errors
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type '?u8'\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const f32, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type '?f32'\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const @Vector(3, u8), gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type '?@Vector(3, u8)'\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const bool, gpa, "10", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected type '?bool'\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const struct { a: i32 }, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional struct\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const struct { i32 }, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional tuple\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const union { x: void }, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional union\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const [3]u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(?[3]u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const []u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(?[]u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const []const u8, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional string\n", "{}", .{status});
+ }
+
+ {
+ var status: Status = .{};
+ defer status.deinit(gpa);
+ try std.testing.expectError(
+ error.ParseZon,
+ fromSlice(*const ?*const enum { foo }, gpa, "true", &status, .{}),
+ );
+ try std.testing.expectFmt("1:1: error: expected optional enum literal\n", "{}", .{status});
+ }
+}
diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig
new file mode 100644
index 0000000000..a3f2b9cc00
--- /dev/null
+++ b/lib/std/zon/stringify.zig
@@ -0,0 +1,2306 @@
+//! ZON can be serialized with `serialize`.
+//!
+//! The following functions are provided for serializing recursive types:
+//! * `serializeMaxDepth`
+//! * `serializeArbitraryDepth`
+//!
+//! For additional control over serialization, see `Serializer`.
+//!
+//! The following types and any types that contain them may not be serialized:
+//! * `type`
+//! * `void`, except as a union payload
+//! * `noreturn`
+//! * Error sets/error unions
+//! * Untagged unions
+//! * Many-pointers or C-pointers
+//! * Opaque types, including `anyopaque`
+//! * Async frame types, including `anyframe` and `anyframe->T`
+//! * Functions
+//!
+//! All other types are valid. Unsupported types will fail to serialize at compile time. Pointers
+//! are followed.
+
+const std = @import("std");
+const assert = std.debug.assert;
+
+/// Options for `serialize`.
+pub const SerializeOptions = struct {
+ /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style.
+ whitespace: bool = true,
+ /// Determines when to emit Unicode code point literals as opposed to integer literals.
+ emit_codepoint_literals: EmitCodepointLiterals = .never,
+ /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers.
+ /// Otherwise they are serialized as string literals.
+ emit_strings_as_containers: bool = false,
+ /// If false, struct fields are not written if they are equal to their default value. Comparison
+ /// is done by `std.meta.eql`.
+ emit_default_optional_fields: bool = true,
+};
+
+/// Serialize the given value as ZON.
+///
+/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type.
+pub fn serialize(
+ val: anytype,
+ options: SerializeOptions,
+ writer: anytype,
+) @TypeOf(writer).Error!void {
+ var sz = serializer(writer, .{
+ .whitespace = options.whitespace,
+ });
+ try sz.value(val, .{
+ .emit_codepoint_literals = options.emit_codepoint_literals,
+ .emit_strings_as_containers = options.emit_strings_as_containers,
+ .emit_default_optional_fields = options.emit_default_optional_fields,
+ });
+}
+
+/// Like `serialize`, but recursive types are allowed.
+///
+/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. Every nested value adds one to a
+/// value's depth.
+pub fn serializeMaxDepth(
+ val: anytype,
+ options: SerializeOptions,
+ writer: anytype,
+ depth: usize,
+) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void {
+ var sz = serializer(writer, .{
+ .whitespace = options.whitespace,
+ });
+ try sz.valueMaxDepth(val, .{
+ .emit_codepoint_literals = options.emit_codepoint_literals,
+ .emit_strings_as_containers = options.emit_strings_as_containers,
+ .emit_default_optional_fields = options.emit_default_optional_fields,
+ }, depth);
+}
+
+/// Like `serialize`, but recursive types are allowed.
+///
+/// It is the caller's responsibility to ensure that `val` does not contain cycles.
+pub fn serializeArbitraryDepth(
+ val: anytype,
+ options: SerializeOptions,
+ writer: anytype,
+) @TypeOf(writer).Error!void {
+ var sz = serializer(writer, .{
+ .whitespace = options.whitespace,
+ });
+ try sz.valueArbitraryDepth(val, .{
+ .emit_codepoint_literals = options.emit_codepoint_literals,
+ .emit_strings_as_containers = options.emit_strings_as_containers,
+ .emit_default_optional_fields = options.emit_default_optional_fields,
+ });
+}
+
+fn typeIsRecursive(comptime T: type) bool {
+ return comptime typeIsRecursiveImpl(T, &.{});
+}
+
+fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool {
+ for (prev_visited) |V| {
+ if (V == T) return true;
+ }
+ const visited = prev_visited ++ .{T};
+
+ return switch (@typeInfo(T)) {
+ .pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited),
+ .optional => |optional| typeIsRecursiveImpl(optional.child, visited),
+ .array => |array| typeIsRecursiveImpl(array.child, visited),
+ .vector => |vector| typeIsRecursiveImpl(vector.child, visited),
+ .@"struct" => |@"struct"| for (@"struct".fields) |field| {
+ if (typeIsRecursiveImpl(field.type, visited)) break true;
+ } else false,
+ .@"union" => |@"union"| inline for (@"union".fields) |field| {
+ if (typeIsRecursiveImpl(field.type, visited)) break true;
+ } else false,
+ else => false,
+ };
+}
+
+fn canSerializeType(T: type) bool {
+ comptime return canSerializeTypeInner(T, &.{}, false);
+}
+
+fn canSerializeTypeInner(
+ 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,
+ .comptime_float,
+ .comptime_int,
+ .null,
+ .enum_literal,
+ => true,
+
+ .noreturn,
+ .void,
+ .type,
+ .undefined,
+ .error_union,
+ .error_set,
+ .@"fn",
+ .frame,
+ .@"anyframe",
+ .@"opaque",
+ => false,
+
+ .@"enum" => |@"enum"| @"enum".is_exhaustive,
+
+ .pointer => |pointer| switch (pointer.size) {
+ .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
+ .slice => canSerializeTypeInner(pointer.child, visited, false),
+ .many, .c => false,
+ },
+
+ .optional => |optional| if (parent_is_optional)
+ false
+ else
+ canSerializeTypeInner(optional.child, visited, true),
+
+ .array => |array| canSerializeTypeInner(array.child, visited, false),
+ .vector => |vector| canSerializeTypeInner(vector.child, visited, false),
+
+ .@"struct" => |@"struct"| {
+ for (visited) |V| if (T == V) return true;
+ const new_visited = visited ++ .{T};
+ for (@"struct".fields) |field| {
+ if (!canSerializeTypeInner(field.type, new_visited, false)) return false;
+ }
+ return true;
+ },
+ .@"union" => |@"union"| {
+ for (visited) |V| if (T == V) return true;
+ const new_visited = visited ++ .{T};
+ if (@"union".tag_type == null) return false;
+ for (@"union".fields) |field| {
+ if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ };
+}
+
+fn isNestedOptional(T: type) bool {
+ comptime switch (@typeInfo(T)) {
+ .optional => |optional| return isNestedOptionalInner(optional.child),
+ else => return false,
+ };
+}
+
+fn isNestedOptionalInner(T: type) bool {
+ switch (@typeInfo(T)) {
+ .pointer => |pointer| {
+ if (pointer.size == .one) {
+ return isNestedOptionalInner(pointer.child);
+ } else {
+ return false;
+ }
+ },
+ .optional => return true,
+ else => return false,
+ }
+}
+
+test "std.zon stringify canSerializeType" {
+ try std.testing.expect(!comptime canSerializeType(void));
+ try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
+ try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
+ try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
+ try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
+ try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
+ try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
+ try std.testing.expect(!comptime canSerializeType(union { foo: void }));
+ try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
+ try std.testing.expect(comptime canSerializeType(comptime_float));
+ try std.testing.expect(comptime canSerializeType(comptime_int));
+ try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
+ try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
+ try std.testing.expect(comptime canSerializeType(?u8));
+ try std.testing.expect(comptime canSerializeType(*?*u8));
+ try std.testing.expect(comptime canSerializeType(?struct {
+ foo: ?struct {
+ ?union(enum) {
+ a: ?@Vector(0, ?*u8),
+ },
+ ?struct {
+ f: ?[]?u8,
+ },
+ },
+ }));
+ try std.testing.expect(!comptime canSerializeType(??u8));
+ try std.testing.expect(!comptime canSerializeType(?*?u8));
+ try std.testing.expect(!comptime canSerializeType(*?*?*u8));
+ try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
+ try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
+ try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
+ try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
+ const Recursive = struct { foo: ?*@This() };
+ try std.testing.expect(comptime canSerializeType(Recursive));
+
+ // Make sure we validate nested optional before we early out due to already having seen
+ // a type recursion!
+ try std.testing.expect(!comptime canSerializeType(struct {
+ add_to_visited: ?u8,
+ retrieve_from_visited: ??u8,
+ }));
+}
+
+test "std.zon typeIsRecursive" {
+ try std.testing.expect(!typeIsRecursive(bool));
+ try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
+ try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
+ try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
+ try std.testing.expect(typeIsRecursive(struct {
+ a: struct {
+ const A = @This();
+ b: struct {
+ c: *struct {
+ a: ?A,
+ },
+ },
+ },
+ }));
+ try std.testing.expect(typeIsRecursive(struct {
+ a: [3]*@This(),
+ }));
+ try std.testing.expect(typeIsRecursive(struct {
+ a: union { a: i32, b: *@This() },
+ }));
+}
+
+fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
+ if (depth == 0) return error.ExceededMaxDepth;
+ const child_depth = depth - 1;
+
+ switch (@typeInfo(@TypeOf(val))) {
+ .pointer => |pointer| switch (pointer.size) {
+ .one => try checkValueDepth(val.*, child_depth),
+ .slice => for (val) |item| {
+ try checkValueDepth(item, child_depth);
+ },
+ .c, .many => {},
+ },
+ .array => for (val) |item| {
+ try checkValueDepth(item, child_depth);
+ },
+ .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| {
+ try checkValueDepth(@field(val, field_info.name), child_depth);
+ },
+ .@"union" => |@"union"| if (@"union".tag_type == null) {
+ return;
+ } else switch (val) {
+ inline else => |payload| {
+ return checkValueDepth(payload, child_depth);
+ },
+ },
+ .optional => if (val) |inner| try checkValueDepth(inner, child_depth),
+ else => {},
+ }
+}
+
+fn expectValueDepthEquals(expected: usize, value: anytype) !void {
+ try checkValueDepth(value, expected);
+ try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1));
+}
+
+test "std.zon checkValueDepth" {
+ try expectValueDepthEquals(1, 10);
+ try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
+ try expectValueDepthEquals(2, .{ 1, 2 });
+ try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
+ try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
+ try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
+ try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
+ try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
+ try expectValueDepthEquals(2, @as(?u32, 1));
+ try expectValueDepthEquals(1, @as(?u32, null));
+ try expectValueDepthEquals(1, null);
+ try expectValueDepthEquals(2, &1);
+ try expectValueDepthEquals(3, &@as(?u32, 1));
+
+ const Union = union(enum) {
+ x: u32,
+ y: struct { x: u32 },
+ };
+ try expectValueDepthEquals(2, Union{ .x = 1 });
+ try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
+
+ const Recurse = struct { r: ?*const @This() };
+ try expectValueDepthEquals(2, Recurse{ .r = null });
+ try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
+ try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
+
+ try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
+ try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
+}
+
+/// Options for `Serializer`.
+pub const SerializerOptions = struct {
+ /// If false, only syntactically necessary whitespace is emitted.
+ whitespace: bool = true,
+};
+
+/// Determines when to emit Unicode code point literals as opposed to integer literals.
+pub const EmitCodepointLiterals = enum {
+ /// Never emit Unicode code point literals.
+ never,
+ /// Emit Unicode code point literals for any `u8` in the printable ASCII range.
+ printable_ascii,
+ /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
+ /// whose value is a valid non-surrogate code point.
+ always,
+
+ /// If the value should be emitted as a Unicode codepoint, return it as a u21.
+ fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
+ // Rule out incompatible integer types
+ switch (@typeInfo(@TypeOf(val))) {
+ .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
+ return null;
+ },
+ .comptime_int => {},
+ else => comptime unreachable,
+ }
+
+ // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
+ // to a u21 if it should.
+ switch (self) {
+ .always => {
+ const c = std.math.cast(u21, val) orelse return null;
+ if (!std.unicode.utf8ValidCodepoint(c)) return null;
+ return c;
+ },
+ .printable_ascii => {
+ const c = std.math.cast(u8, val) orelse return null;
+ if (!std.ascii.isPrint(c)) return null;
+ return c;
+ },
+ .never => {
+ return null;
+ },
+ }
+ }
+};
+
+/// Options for serialization of an individual value.
+///
+/// See `SerializeOptions` for more information on these options.
+pub const ValueOptions = struct {
+ emit_codepoint_literals: EmitCodepointLiterals = .never,
+ emit_strings_as_containers: bool = false,
+ emit_default_optional_fields: bool = true,
+};
+
+/// Options for manual serialization of container types.
+pub const SerializeContainerOptions = struct {
+ /// The whitespace style that should be used for this container. Ignored if whitespace is off.
+ whitespace_style: union(enum) {
+ /// If true, wrap every field. If false do not.
+ wrap: bool,
+ /// Automatically decide whether to wrap or not based on the number of fields. Following
+ /// the standard rule of thumb, containers with more than two fields are wrapped.
+ fields: usize,
+ } = .{ .wrap = true },
+
+ fn shouldWrap(self: SerializeContainerOptions) bool {
+ return switch (self.whitespace_style) {
+ .wrap => |wrap| wrap,
+ .fields => |fields| fields > 2,
+ };
+ }
+};
+
+/// Lower level control over serialization, you can create a new instance with `serializer`.
+///
+/// Useful when you want control over which fields are serialized, how they're represented,
+/// or want to write a ZON object that does not exist in memory.
+///
+/// You can serialize values with `value`. To serialize recursive types, the following are provided:
+/// * `valueMaxDepth`
+/// * `valueArbitraryDepth`
+///
+/// You can also serialize values using specific notations:
+/// * `int`
+/// * `float`
+/// * `codePoint`
+/// * `tuple`
+/// * `tupleMaxDepth`
+/// * `tupleArbitraryDepth`
+/// * `string`
+/// * `multilineString`
+///
+/// For manual serialization of containers, see:
+/// * `startStruct`
+/// * `startTuple`
+///
+/// # Example
+/// ```zig
+/// var sz = serializer(writer, .{});
+/// var vec2 = try sz.startStruct(.{});
+/// try vec2.field("x", 1.5, .{});
+/// try vec2.fieldPrefix();
+/// try sz.value(2.5);
+/// try vec2.finish();
+/// ```
+pub fn Serializer(Writer: type) type {
+ return struct {
+ const Self = @This();
+
+ options: SerializerOptions,
+ indent_level: u8,
+ writer: Writer,
+
+ /// Initialize a serializer.
+ fn init(writer: Writer, options: SerializerOptions) Self {
+ return .{
+ .options = options,
+ .writer = writer,
+ .indent_level = 0,
+ };
+ }
+
+ /// Serialize a value, similar to `serialize`.
+ pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
+ comptime assert(!typeIsRecursive(@TypeOf(val)));
+ return self.valueArbitraryDepth(val, options);
+ }
+
+ /// Serialize a value, similar to `serializeMaxDepth`.
+ pub fn valueMaxDepth(
+ self: *Self,
+ val: anytype,
+ options: ValueOptions,
+ depth: usize,
+ ) (Writer.Error || error{ExceededMaxDepth})!void {
+ try checkValueDepth(val, depth);
+ return self.valueArbitraryDepth(val, options);
+ }
+
+ /// Serialize a value, similar to `serializeArbitraryDepth`.
+ pub fn valueArbitraryDepth(
+ self: *Self,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ comptime assert(canSerializeType(@TypeOf(val)));
+ switch (@typeInfo(@TypeOf(val))) {
+ .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
+ self.codePoint(c) catch |err| switch (err) {
+ error.InvalidCodepoint => unreachable, // Already validated
+ else => |e| return e,
+ };
+ } else {
+ try self.int(val);
+ },
+ .float, .comptime_float => try self.float(val),
+ .bool, .null => try std.fmt.format(self.writer, "{}", .{val}),
+ .enum_literal => try self.ident(@tagName(val)),
+ .@"enum" => try self.ident(@tagName(val)),
+ .void => try self.writer.writeAll("{}"),
+ .pointer => |pointer| {
+ // Try to serialize as a string
+ const item: ?type = switch (@typeInfo(pointer.child)) {
+ .array => |array| array.child,
+ else => if (pointer.size == .slice) pointer.child else null,
+ };
+ if (item == u8 and
+ (pointer.sentinel() == null or pointer.sentinel() == 0) and
+ !options.emit_strings_as_containers)
+ {
+ return try self.string(val);
+ }
+
+ // Serialize as either a tuple or as the child type
+ switch (pointer.size) {
+ .slice => try self.tupleImpl(val, options),
+ .one => try self.valueArbitraryDepth(val.*, options),
+ else => comptime unreachable,
+ }
+ },
+ .array => {
+ var container = try self.startTuple(
+ .{ .whitespace_style = .{ .fields = val.len } },
+ );
+ for (val) |item_val| {
+ try container.fieldArbitraryDepth(item_val, options);
+ }
+ try container.finish();
+ },
+ .@"struct" => |@"struct"| if (@"struct".is_tuple) {
+ var container = try self.startTuple(
+ .{ .whitespace_style = .{ .fields = @"struct".fields.len } },
+ );
+ inline for (val) |field_value| {
+ try container.fieldArbitraryDepth(field_value, options);
+ }
+ try container.finish();
+ } else {
+ // Decide which fields to emit
+ const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
+ break :b .{ @"struct".fields.len, @splat(false) };
+ } else b: {
+ var fields = @"struct".fields.len;
+ var skipped: [@"struct".fields.len]bool = @splat(false);
+ inline for (@"struct".fields, &skipped) |field_info, *skip| {
+ if (field_info.default_value_ptr) |ptr| {
+ const default: *const field_info.type = @ptrCast(@alignCast(ptr));
+ const field_value = @field(val, field_info.name);
+ if (std.meta.eql(field_value, default.*)) {
+ skip.* = true;
+ fields -= 1;
+ }
+ }
+ }
+ break :b .{ fields, skipped };
+ };
+
+ // Emit those fields
+ var container = try self.startStruct(
+ .{ .whitespace_style = .{ .fields = fields } },
+ );
+ inline for (@"struct".fields, skipped) |field_info, skip| {
+ if (!skip) {
+ try container.fieldArbitraryDepth(
+ field_info.name,
+ @field(val, field_info.name),
+ options,
+ );
+ }
+ }
+ try container.finish();
+ },
+ .@"union" => |@"union"| {
+ comptime assert(@"union".tag_type != null);
+ var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } });
+ switch (val) {
+ inline else => |pl, tag| try container.fieldArbitraryDepth(
+ @tagName(tag),
+ pl,
+ options,
+ ),
+ }
+ try container.finish();
+ },
+ .optional => if (val) |inner| {
+ try self.valueArbitraryDepth(inner, options);
+ } else {
+ try self.writer.writeAll("null");
+ },
+ .vector => |vector| {
+ var container = try self.startTuple(
+ .{ .whitespace_style = .{ .fields = vector.len } },
+ );
+ for (0..vector.len) |i| {
+ try container.fieldArbitraryDepth(val[i], options);
+ }
+ try container.finish();
+ },
+
+ else => comptime unreachable,
+ }
+ }
+
+ /// Serialize an integer.
+ pub fn int(self: *Self, val: anytype) Writer.Error!void {
+ try std.fmt.formatInt(val, 10, .lower, .{}, self.writer);
+ }
+
+ /// Serialize a float.
+ pub fn float(self: *Self, val: anytype) Writer.Error!void {
+ switch (@typeInfo(@TypeOf(val))) {
+ .float => if (std.math.isNan(val)) {
+ return self.writer.writeAll("nan");
+ } else if (std.math.isPositiveInf(val)) {
+ return self.writer.writeAll("inf");
+ } else if (std.math.isNegativeInf(val)) {
+ return self.writer.writeAll("-inf");
+ } else {
+ try std.fmt.format(self.writer, "{d}", .{val});
+ },
+ .comptime_float => try std.fmt.format(self.writer, "{d}", .{val}),
+ else => comptime unreachable,
+ }
+ }
+
+ /// Serialize `name` as an identifier prefixed with `.`.
+ ///
+ /// Escapes the identifier if necessary.
+ pub fn ident(self: *Self, name: []const u8) Writer.Error!void {
+ try self.writer.print(".{p_}", .{std.zig.fmtId(name)});
+ }
+
+ /// Serialize `val` as a Unicode codepoint.
+ ///
+ /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
+ pub fn codePoint(
+ self: *Self,
+ val: u21,
+ ) (Writer.Error || error{InvalidCodepoint})!void {
+ var buf: [8]u8 = undefined;
+ const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint;
+ const str = buf[0..len];
+ try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)});
+ }
+
+ /// Like `value`, but always serializes `val` as a tuple.
+ ///
+ /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
+ pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
+ comptime assert(!typeIsRecursive(@TypeOf(val)));
+ try self.tupleArbitraryDepth(val, options);
+ }
+
+ /// Like `tuple`, but recursive types are allowed.
+ ///
+ /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
+ pub fn tupleMaxDepth(
+ self: *Self,
+ val: anytype,
+ options: ValueOptions,
+ depth: usize,
+ ) (Writer.Error || error{ExceededMaxDepth})!void {
+ try checkValueDepth(val, depth);
+ try self.tupleArbitraryDepth(val, options);
+ }
+
+ /// Like `tuple`, but recursive types are allowed.
+ ///
+ /// It is the caller's responsibility to ensure that `val` does not contain cycles.
+ pub fn tupleArbitraryDepth(
+ self: *Self,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.tupleImpl(val, options);
+ }
+
+ fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void {
+ comptime assert(canSerializeType(@TypeOf(val)));
+ switch (@typeInfo(@TypeOf(val))) {
+ .@"struct" => {
+ var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } });
+ inline for (val) |item_val| {
+ try container.fieldArbitraryDepth(item_val, options);
+ }
+ try container.finish();
+ },
+ .pointer, .array => {
+ var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } });
+ for (val) |item_val| {
+ try container.fieldArbitraryDepth(item_val, options);
+ }
+ try container.finish();
+ },
+ else => comptime unreachable,
+ }
+ }
+
+ /// Like `value`, but always serializes `val` as a string.
+ pub fn string(self: *Self, val: []const u8) Writer.Error!void {
+ try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)});
+ }
+
+ /// Options for formatting multiline strings.
+ pub const MultilineStringOptions = struct {
+ /// If top level is true, whitespace before and after the multiline string is elided.
+ /// If it is true, a newline is printed, then the value, followed by a newline, and if
+ /// whitespace is true any necessary indentation follows.
+ top_level: bool = false,
+ };
+
+ /// Like `value`, but always serializes to a multiline string literal.
+ ///
+ /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
+ /// since multiline strings cannot represent CR without a following newline.
+ pub fn multilineString(
+ self: *Self,
+ val: []const u8,
+ options: MultilineStringOptions,
+ ) (Writer.Error || error{InnerCarriageReturn})!void {
+ // Make sure the string does not contain any carriage returns not followed by a newline
+ var i: usize = 0;
+ while (i < val.len) : (i += 1) {
+ if (val[i] == '\r') {
+ if (i + 1 < val.len) {
+ if (val[i + 1] == '\n') {
+ i += 1;
+ continue;
+ }
+ }
+ return error.InnerCarriageReturn;
+ }
+ }
+
+ if (!options.top_level) {
+ try self.newline();
+ try self.indent();
+ }
+
+ try self.writer.writeAll("\\\\");
+ for (val) |c| {
+ if (c != '\r') {
+ try self.writer.writeByte(c); // We write newlines here even if whitespace off
+ if (c == '\n') {
+ try self.indent();
+ try self.writer.writeAll("\\\\");
+ }
+ }
+ }
+
+ if (!options.top_level) {
+ try self.writer.writeByte('\n'); // Even if whitespace off
+ try self.indent();
+ }
+ }
+
+ /// Create a `Struct` for writing ZON structs field by field.
+ pub fn startStruct(
+ self: *Self,
+ options: SerializeContainerOptions,
+ ) Writer.Error!Struct {
+ return Struct.start(self, options);
+ }
+
+ /// Creates a `Tuple` for writing ZON tuples field by field.
+ pub fn startTuple(
+ self: *Self,
+ options: SerializeContainerOptions,
+ ) Writer.Error!Tuple {
+ return Tuple.start(self, options);
+ }
+
+ fn indent(self: *Self) Writer.Error!void {
+ if (self.options.whitespace) {
+ try self.writer.writeByteNTimes(' ', 4 * self.indent_level);
+ }
+ }
+
+ fn newline(self: *Self) Writer.Error!void {
+ if (self.options.whitespace) {
+ try self.writer.writeByte('\n');
+ }
+ }
+
+ fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void {
+ if (self.containerShouldWrap(len)) {
+ try self.newline();
+ } else {
+ try self.space();
+ }
+ }
+
+ fn space(self: *Self) Writer.Error!void {
+ if (self.options.whitespace) {
+ try self.writer.writeByte(' ');
+ }
+ }
+
+ /// Writes ZON tuples field by field.
+ pub const Tuple = struct {
+ container: Container,
+
+ fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple {
+ return .{
+ .container = try Container.start(parent, .anon, options),
+ };
+ }
+
+ /// Finishes serializing the tuple.
+ ///
+ /// Prints a trailing comma as configured when appropriate, and the closing bracket.
+ pub fn finish(self: *Tuple) Writer.Error!void {
+ try self.container.finish();
+ self.* = undefined;
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
+ pub fn field(
+ self: *Tuple,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.container.field(null, val, options);
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
+ pub fn fieldMaxDepth(
+ self: *Tuple,
+ val: anytype,
+ options: ValueOptions,
+ depth: usize,
+ ) (Writer.Error || error{ExceededMaxDepth})!void {
+ try self.container.fieldMaxDepth(null, val, options, depth);
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
+ /// `valueArbitraryDepth`.
+ pub fn fieldArbitraryDepth(
+ self: *Tuple,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.container.fieldArbitraryDepth(null, val, options);
+ }
+
+ /// Print a field prefix. This prints any necessary commas, and whitespace as
+ /// configured. Useful if you want to serialize the field value yourself.
+ pub fn fieldPrefix(self: *Tuple) Writer.Error!void {
+ try self.container.fieldPrefix(null);
+ }
+ };
+
+ /// Writes ZON structs field by field.
+ pub const Struct = struct {
+ container: Container,
+
+ fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct {
+ return .{
+ .container = try Container.start(parent, .named, options),
+ };
+ }
+
+ /// Finishes serializing the struct.
+ ///
+ /// Prints a trailing comma as configured when appropriate, and the closing bracket.
+ pub fn finish(self: *Struct) Writer.Error!void {
+ try self.container.finish();
+ self.* = undefined;
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
+ pub fn field(
+ self: *Struct,
+ name: []const u8,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.container.field(name, val, options);
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
+ pub fn fieldMaxDepth(
+ self: *Struct,
+ name: []const u8,
+ val: anytype,
+ options: ValueOptions,
+ depth: usize,
+ ) (Writer.Error || error{ExceededMaxDepth})!void {
+ try self.container.fieldMaxDepth(name, val, options, depth);
+ }
+
+ /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
+ /// `valueArbitraryDepth`.
+ pub fn fieldArbitraryDepth(
+ self: *Struct,
+ name: []const u8,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.container.fieldArbitraryDepth(name, val, options);
+ }
+
+ /// Print a field prefix. This prints any necessary commas, the field name (escaped if
+ /// necessary) and whitespace as configured. Useful if you want to serialize the field
+ /// value yourself.
+ pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void {
+ try self.container.fieldPrefix(name);
+ }
+ };
+
+ const Container = struct {
+ const FieldStyle = enum { named, anon };
+
+ serializer: *Self,
+ field_style: FieldStyle,
+ options: SerializeContainerOptions,
+ empty: bool,
+
+ fn start(
+ sz: *Self,
+ field_style: FieldStyle,
+ options: SerializeContainerOptions,
+ ) Writer.Error!Container {
+ if (options.shouldWrap()) sz.indent_level +|= 1;
+ try sz.writer.writeAll(".{");
+ return .{
+ .serializer = sz,
+ .field_style = field_style,
+ .options = options,
+ .empty = true,
+ };
+ }
+
+ fn finish(self: *Container) Writer.Error!void {
+ if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
+ if (!self.empty) {
+ if (self.options.shouldWrap()) {
+ if (self.serializer.options.whitespace) {
+ try self.serializer.writer.writeByte(',');
+ }
+ try self.serializer.newline();
+ try self.serializer.indent();
+ } else if (!self.shouldElideSpaces()) {
+ try self.serializer.space();
+ }
+ }
+ try self.serializer.writer.writeByte('}');
+ self.* = undefined;
+ }
+
+ fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void {
+ if (!self.empty) {
+ try self.serializer.writer.writeByte(',');
+ }
+ self.empty = false;
+ if (self.options.shouldWrap()) {
+ try self.serializer.newline();
+ } else if (!self.shouldElideSpaces()) {
+ try self.serializer.space();
+ }
+ if (self.options.shouldWrap()) try self.serializer.indent();
+ if (name) |n| {
+ try self.serializer.ident(n);
+ try self.serializer.space();
+ try self.serializer.writer.writeByte('=');
+ try self.serializer.space();
+ }
+ }
+
+ fn field(
+ self: *Container,
+ name: ?[]const u8,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ comptime assert(!typeIsRecursive(@TypeOf(val)));
+ try self.fieldArbitraryDepth(name, val, options);
+ }
+
+ fn fieldMaxDepth(
+ self: *Container,
+ name: ?[]const u8,
+ val: anytype,
+ options: ValueOptions,
+ depth: usize,
+ ) (Writer.Error || error{ExceededMaxDepth})!void {
+ try checkValueDepth(val, depth);
+ try self.fieldArbitraryDepth(name, val, options);
+ }
+
+ fn fieldArbitraryDepth(
+ self: *Container,
+ name: ?[]const u8,
+ val: anytype,
+ options: ValueOptions,
+ ) Writer.Error!void {
+ try self.fieldPrefix(name);
+ try self.serializer.valueArbitraryDepth(val, options);
+ }
+
+ fn shouldElideSpaces(self: *const Container) bool {
+ return switch (self.options.whitespace_style) {
+ .fields => |fields| self.field_style != .named and fields == 1,
+ else => false,
+ };
+ }
+ };
+ };
+}
+
+/// Creates a new `Serializer` with the given writer and options.
+pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) {
+ return .init(writer, options);
+}
+
+fn expectSerializeEqual(
+ expected: []const u8,
+ value: anytype,
+ options: SerializeOptions,
+) !void {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ try serialize(value, options, buf.writer());
+ try std.testing.expectEqualStrings(expected, buf.items);
+}
+
+test "std.zon stringify whitespace, high level API" {
+ try expectSerializeEqual(".{}", .{}, .{});
+ try expectSerializeEqual(".{}", .{}, .{ .whitespace = false });
+
+ try expectSerializeEqual(".{1}", .{1}, .{});
+ try expectSerializeEqual(".{1}", .{1}, .{ .whitespace = false });
+
+ try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{});
+ try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false });
+
+ try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{});
+ try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{ .whitespace = false });
+
+ try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{});
+ try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false });
+
+ try expectSerializeEqual(".{ 1, 2 }", .{ 1, 2 }, .{});
+ try expectSerializeEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false });
+
+ try expectSerializeEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{});
+ try expectSerializeEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false });
+
+ try expectSerializeEqual(".{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{});
+ try expectSerializeEqual(".{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false });
+
+ try expectSerializeEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{});
+ try expectSerializeEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false });
+
+ try expectSerializeEqual(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}
+ , .{ 1, 2, 3 }, .{});
+ try expectSerializeEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false });
+
+ try expectSerializeEqual(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}
+ , @as([3]u32, .{ 1, 2, 3 }), .{});
+ try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false });
+
+ try expectSerializeEqual(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}
+ , @as([]const u32, &.{ 1, 2, 3 }), .{});
+ try expectSerializeEqual(
+ ".{1,2,3}",
+ @as([]const u32, &.{ 1, 2, 3 }),
+ .{ .whitespace = false },
+ );
+
+ try expectSerializeEqual(
+ \\.{
+ \\ .x = 1,
+ \\ .y = 2,
+ \\ .z = 3,
+ \\}
+ , .{ .x = 1, .y = 2, .z = 3 }, .{});
+ try expectSerializeEqual(
+ ".{.x=1,.y=2,.z=3}",
+ .{ .x = 1, .y = 2, .z = 3 },
+ .{ .whitespace = false },
+ );
+
+ const Union = union(enum) { a: bool, b: i32, c: u8 };
+
+ try expectSerializeEqual(".{ .b = 1 }", Union{ .b = 1 }, .{});
+ try expectSerializeEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false });
+
+ // Nested indentation where outer object doesn't wrap
+ try expectSerializeEqual(
+ \\.{ .inner = .{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\} }
+ , .{ .inner = .{ 1, 2, 3 } }, .{});
+}
+
+test "std.zon stringify whitespace, low level API" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ inline for (.{ true, false }) |whitespace| {
+ sz.options = .{ .whitespace = whitespace };
+
+ // Empty containers
+ {
+ var container = try sz.startStruct(.{});
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{});
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 0 } });
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 0 } });
+ try container.finish();
+ try std.testing.expectEqualStrings(".{}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Size 1
+ {
+ var container = try sz.startStruct(.{});
+ try container.field("a", 1, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ .a = 1,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{});
+ try container.field(1, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 1,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field("a", 1, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ // We get extra spaces here, since we didn't know up front that there would only be one
+ // field.
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field(1, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ 1 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 1 } });
+ try container.field("a", 1, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 1 } });
+ try container.field(1, .{});
+ try container.finish();
+ try std.testing.expectEqualStrings(".{1}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Size 2
+ {
+ var container = try sz.startStruct(.{});
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ .a = 1,
+ \\ .b = 2,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{});
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 2 } });
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 2 } });
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ // Size 3
+ {
+ var container = try sz.startStruct(.{});
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.field("c", 3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ .a = 1,
+ \\ .b = 2,
+ \\ .c = 3,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{});
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.field(3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.field("c", 3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.field(3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 3 } });
+ try container.field("a", 1, .{});
+ try container.field("b", 2, .{});
+ try container.field("c", 3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ .a = 1,
+ \\ .b = 2,
+ \\ .c = 3,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 3 } });
+ try container.field(1, .{});
+ try container.field(2, .{});
+ try container.field(3, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(".{1,2,3}", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ // Nested objects where the outer container doesn't wrap but the inner containers do
+ {
+ var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } });
+ try container.field("first", .{ 1, 2, 3 }, .{});
+ try container.field("second", .{ 4, 5, 6 }, .{});
+ try container.finish();
+ if (whitespace) {
+ try std.testing.expectEqualStrings(
+ \\.{ .first = .{
+ \\ 1,
+ \\ 2,
+ \\ 3,
+ \\}, .second = .{
+ \\ 4,
+ \\ 5,
+ \\ 6,
+ \\} }
+ , buf.items);
+ } else {
+ try std.testing.expectEqualStrings(
+ ".{.first=.{1,2,3},.second=.{4,5,6}}",
+ buf.items,
+ );
+ }
+ buf.clearRetainingCapacity();
+ }
+ }
+}
+
+test "std.zon stringify utf8 codepoints" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ // Printable ASCII
+ try sz.int('a');
+ try std.testing.expectEqualStrings("97", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.codePoint('a');
+ try std.testing.expectEqualStrings("'a'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('a', .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings("'a'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii });
+ try std.testing.expectEqualStrings("'a'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('a', .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings("97", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Short escaped codepoint
+ try sz.int('\n');
+ try std.testing.expectEqualStrings("10", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.codePoint('\n');
+ try std.testing.expectEqualStrings("'\\n'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('\n', .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings("'\\n'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii });
+ try std.testing.expectEqualStrings("10", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('\n', .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings("10", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Large codepoint
+ try sz.int('⚡');
+ try std.testing.expectEqualStrings("9889", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.codePoint('⚡');
+ try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('⚡', .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('⚡', .{ .emit_codepoint_literals = .printable_ascii });
+ try std.testing.expectEqualStrings("9889", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value('⚡', .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings("9889", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Invalid codepoint
+ try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1));
+
+ try sz.int(0x110000 + 1);
+ try std.testing.expectEqualStrings("1114113", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings("1114113", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii });
+ try std.testing.expectEqualStrings("1114113", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings("1114113", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Valid codepoint, not a codepoint type
+ try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings("97", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii });
+ try std.testing.expectEqualStrings("97", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings("97", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Make sure value options are passed to children
+ try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .always });
+ try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(.{ .c = '⚡' }, .{ .emit_codepoint_literals = .never });
+ try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items);
+ buf.clearRetainingCapacity();
+}
+
+test "std.zon stringify strings" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ // Minimal case
+ try sz.string("abc⚡\n");
+ try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.tuple("abc⚡\n", .{});
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 97,
+ \\ 98,
+ \\ 99,
+ \\ 226,
+ \\ 154,
+ \\ 161,
+ \\ 10,
+ \\}
+ , buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value("abc⚡\n", .{});
+ try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value("abc⚡\n", .{ .emit_strings_as_containers = true });
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 97,
+ \\ 98,
+ \\ 99,
+ \\ 226,
+ \\ 154,
+ \\ 161,
+ \\ 10,
+ \\}
+ , buf.items);
+ buf.clearRetainingCapacity();
+
+ // Value options are inherited by children
+ try sz.value(.{ .str = "abc" }, .{});
+ try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true });
+ try std.testing.expectEqualStrings(
+ \\.{ .str = .{
+ \\ 97,
+ \\ 98,
+ \\ 99,
+ \\} }
+ , buf.items);
+ buf.clearRetainingCapacity();
+
+ // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can
+ // round trip correctly.
+ try sz.value("abc".*, .{});
+ try std.testing.expectEqualStrings(
+ \\.{
+ \\ 97,
+ \\ 98,
+ \\ 99,
+ \\}
+ , buf.items);
+ buf.clearRetainingCapacity();
+}
+
+test "std.zon stringify multiline strings" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ inline for (.{ true, false }) |whitespace| {
+ sz.options.whitespace = whitespace;
+
+ {
+ try sz.multilineString("", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("abc⚡", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\abc⚡", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("abc⚡\ndef", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("abc⚡\r\ndef", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("\nabc⚡", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("\r\nabc⚡", .{ .top_level = true });
+ try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try sz.multilineString("abc\ndef", .{});
+ if (whitespace) {
+ try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items);
+ } else {
+ try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", buf.items);
+ }
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ const str: []const u8 = &.{ 'a', '\r', 'c' };
+ try sz.string(str);
+ try std.testing.expectEqualStrings("\"a\\rc\"", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ {
+ try std.testing.expectError(
+ error.InnerCarriageReturn,
+ sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}),
+ );
+ try std.testing.expectError(
+ error.InnerCarriageReturn,
+ sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}),
+ );
+ try std.testing.expectError(
+ error.InnerCarriageReturn,
+ sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+ }
+ }
+}
+
+test "std.zon stringify skip default fields" {
+ const Struct = struct {
+ x: i32 = 2,
+ y: i8,
+ z: u32 = 4,
+ inner1: struct { a: u8 = 'z', b: u8 = 'y', c: u8 } = .{
+ .a = '1',
+ .b = '2',
+ .c = '3',
+ },
+ inner2: struct { u8, u8, u8 } = .{
+ 'a',
+ 'b',
+ 'c',
+ },
+ inner3: struct { u8, u8, u8 } = .{
+ 'a',
+ 'b',
+ 'c',
+ },
+ };
+
+ // Not skipping if not set
+ try expectSerializeEqual(
+ \\.{
+ \\ .x = 2,
+ \\ .y = 3,
+ \\ .z = 4,
+ \\ .inner1 = .{
+ \\ .a = '1',
+ \\ .b = '2',
+ \\ .c = '3',
+ \\ },
+ \\ .inner2 = .{
+ \\ 'a',
+ \\ 'b',
+ \\ 'c',
+ \\ },
+ \\ .inner3 = .{
+ \\ 'a',
+ \\ 'b',
+ \\ 'd',
+ \\ },
+ \\}
+ ,
+ Struct{
+ .y = 3,
+ .z = 4,
+ .inner1 = .{
+ .a = '1',
+ .b = '2',
+ .c = '3',
+ },
+ .inner3 = .{
+ 'a',
+ 'b',
+ 'd',
+ },
+ },
+ .{ .emit_codepoint_literals = .always },
+ );
+
+ // Top level defaults
+ try expectSerializeEqual(
+ \\.{ .y = 3, .inner3 = .{
+ \\ 'a',
+ \\ 'b',
+ \\ 'd',
+ \\} }
+ ,
+ Struct{
+ .y = 3,
+ .z = 4,
+ .inner1 = .{
+ .a = '1',
+ .b = '2',
+ .c = '3',
+ },
+ .inner3 = .{
+ 'a',
+ 'b',
+ 'd',
+ },
+ },
+ .{
+ .emit_default_optional_fields = false,
+ .emit_codepoint_literals = .always,
+ },
+ );
+
+ // Inner types having defaults, and defaults changing the number of fields affecting the
+ // formatting
+ try expectSerializeEqual(
+ \\.{
+ \\ .y = 3,
+ \\ .inner1 = .{ .b = '2', .c = '3' },
+ \\ .inner3 = .{
+ \\ 'a',
+ \\ 'b',
+ \\ 'd',
+ \\ },
+ \\}
+ ,
+ Struct{
+ .y = 3,
+ .z = 4,
+ .inner1 = .{
+ .a = 'z',
+ .b = '2',
+ .c = '3',
+ },
+ .inner3 = .{
+ 'a',
+ 'b',
+ 'd',
+ },
+ },
+ .{
+ .emit_default_optional_fields = false,
+ .emit_codepoint_literals = .always,
+ },
+ );
+
+ const DefaultStrings = struct {
+ foo: []const u8 = "abc",
+ };
+ try expectSerializeEqual(
+ \\.{}
+ ,
+ DefaultStrings{ .foo = "abc" },
+ .{ .emit_default_optional_fields = false },
+ );
+ try expectSerializeEqual(
+ \\.{ .foo = "abcd" }
+ ,
+ DefaultStrings{ .foo = "abcd" },
+ .{ .emit_default_optional_fields = false },
+ );
+}
+
+test "std.zon depth limits" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+
+ const Recurse = struct { r: []const @This() };
+
+ // Normal operation
+ try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16);
+ try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
+ buf.clearRetainingCapacity();
+
+ try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer());
+ try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Max depth failing on non recursive type
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Max depth passing on recursive type
+ {
+ const maybe_recurse = Recurse{ .r = &.{} };
+ try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2);
+ try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Unchecked passing on recursive type
+ {
+ const maybe_recurse = Recurse{ .r = &.{} };
+ try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer());
+ try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Max depth failing on recursive type due to depth
+ {
+ var maybe_recurse = Recurse{ .r = &.{} };
+ maybe_recurse.r = &.{.{ .r = &.{} }};
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Same but for a slice
+ {
+ var temp: [1]Recurse = .{.{ .r = &.{} }};
+ const maybe_recurse: []const Recurse = &temp;
+
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+
+ var sz = serializer(buf.writer(), .{});
+
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ sz.tupleMaxDepth(maybe_recurse, .{}, 2),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.tupleArbitraryDepth(maybe_recurse, .{});
+ try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // A slice succeeding
+ {
+ var temp: [1]Recurse = .{.{ .r = &.{} }};
+ const maybe_recurse: []const Recurse = &temp;
+
+ try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3);
+ try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
+ buf.clearRetainingCapacity();
+
+ var sz = serializer(buf.writer(), .{});
+
+ try sz.tupleMaxDepth(maybe_recurse, .{}, 3);
+ try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.tupleArbitraryDepth(maybe_recurse, .{});
+ try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Max depth failing on recursive type due to recursion
+ {
+ var temp: [1]Recurse = .{.{ .r = &.{} }};
+ temp[0].r = &temp;
+ const maybe_recurse: []const Recurse = &temp;
+
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+
+ var sz = serializer(buf.writer(), .{});
+ try std.testing.expectError(
+ error.ExceededMaxDepth,
+ sz.tupleMaxDepth(maybe_recurse, .{}, 128),
+ );
+ try std.testing.expectEqualStrings("", buf.items);
+ buf.clearRetainingCapacity();
+ }
+
+ // Max depth on other parts of the lower level API
+ {
+ var sz = serializer(buf.writer(), .{});
+
+ const maybe_recurse: []const Recurse = &.{};
+
+ try std.testing.expectError(error.ExceededMaxDepth, sz.valueMaxDepth(1, .{}, 0));
+ try sz.valueMaxDepth(2, .{}, 1);
+ try sz.value(3, .{});
+ try sz.valueArbitraryDepth(maybe_recurse, .{});
+
+ var s = try sz.startStruct(.{});
+ try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0));
+ try s.fieldMaxDepth("b", 4, .{}, 1);
+ try s.field("c", 5, .{});
+ try s.fieldArbitraryDepth("d", maybe_recurse, .{});
+ try s.finish();
+
+ var t = try sz.startTuple(.{});
+ try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0));
+ try t.fieldMaxDepth(6, .{}, 1);
+ try t.field(7, .{});
+ try t.fieldArbitraryDepth(maybe_recurse, .{});
+ try t.finish();
+
+ var a = try sz.startTuple(.{});
+ try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0));
+ try a.fieldMaxDepth(8, .{}, 1);
+ try a.field(9, .{});
+ try a.fieldArbitraryDepth(maybe_recurse, .{});
+ try a.finish();
+
+ try std.testing.expectEqualStrings(
+ \\23.{}.{
+ \\ .b = 4,
+ \\ .c = 5,
+ \\ .d = .{},
+ \\}.{
+ \\ 6,
+ \\ 7,
+ \\ .{},
+ \\}.{
+ \\ 8,
+ \\ 9,
+ \\ .{},
+ \\}
+ , buf.items);
+ }
+}
+
+test "std.zon stringify primitives" {
+ // Issue: https://github.com/ziglang/zig/issues/20880
+ if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest;
+
+ try expectSerializeEqual(
+ \\.{
+ \\ .a = 1.5,
+ \\ .b = 0.3333333333333333333333333333333333,
+ \\ .c = 3.1415926535897932384626433832795028,
+ \\ .d = 0,
+ \\ .e = -0,
+ \\ .f = inf,
+ \\ .g = -inf,
+ \\ .h = nan,
+ \\}
+ ,
+ .{
+ .a = @as(f128, 1.5), // Make sure explicit f128s work
+ .b = 1.0 / 3.0,
+ .c = std.math.pi,
+ .d = 0.0,
+ .e = -0.0,
+ .f = std.math.inf(f32),
+ .g = -std.math.inf(f32),
+ .h = std.math.nan(f32),
+ },
+ .{},
+ );
+
+ try expectSerializeEqual(
+ \\.{
+ \\ .a = 18446744073709551616,
+ \\ .b = -18446744073709551616,
+ \\ .c = 680564733841876926926749214863536422912,
+ \\ .d = -680564733841876926926749214863536422912,
+ \\ .e = 0,
+ \\}
+ ,
+ .{
+ .a = 18446744073709551616,
+ .b = -18446744073709551616,
+ .c = 680564733841876926926749214863536422912,
+ .d = -680564733841876926926749214863536422912,
+ .e = 0,
+ },
+ .{},
+ );
+
+ try expectSerializeEqual(
+ \\.{
+ \\ .a = true,
+ \\ .b = false,
+ \\ .c = .foo,
+ \\ .e = null,
+ \\}
+ ,
+ .{
+ .a = true,
+ .b = false,
+ .c = .foo,
+ .e = null,
+ },
+ .{},
+ );
+
+ const Struct = struct { x: f32, y: f32 };
+ try expectSerializeEqual(
+ ".{ .a = .{ .x = 1, .y = 2 }, .b = null }",
+ .{
+ .a = @as(?Struct, .{ .x = 1, .y = 2 }),
+ .b = @as(?Struct, null),
+ },
+ .{},
+ );
+
+ const E = enum(u8) {
+ foo,
+ bar,
+ };
+ try expectSerializeEqual(
+ ".{ .a = .foo, .b = .foo }",
+ .{
+ .a = .foo,
+ .b = E.foo,
+ },
+ .{},
+ );
+}
+
+test "std.zon stringify ident" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{});
+ try sz.ident("a");
+ try std.testing.expectEqualStrings(".a", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("foo_1");
+ try std.testing.expectEqualStrings(".foo_1", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("_foo_1");
+ try std.testing.expectEqualStrings("._foo_1", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("foo bar");
+ try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("1foo");
+ try std.testing.expectEqualStrings(".@\"1foo\"", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("var");
+ try std.testing.expectEqualStrings(".@\"var\"", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("true");
+ try std.testing.expectEqualStrings(".true", buf.items);
+ buf.clearRetainingCapacity();
+
+ try sz.ident("_");
+ try std.testing.expectEqualStrings("._", buf.items);
+ buf.clearRetainingCapacity();
+
+ const Enum = enum {
+ @"foo bar",
+ };
+ try expectSerializeEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{
+ .@"var" = .@"foo bar",
+ .@"1" = Enum.@"foo bar",
+ }, .{});
+}
+
+test "std.zon stringify as tuple" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ // Tuples
+ try sz.tuple(.{ 1, 2 }, .{});
+ try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Slice
+ try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{});
+ try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Array
+ try sz.tuple([2]u8{ 1, 2 }, .{});
+ try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items);
+ buf.clearRetainingCapacity();
+}
+
+test "std.zon stringify as float" {
+ var buf = std.ArrayList(u8).init(std.testing.allocator);
+ defer buf.deinit();
+ var sz = serializer(buf.writer(), .{});
+
+ // Comptime float
+ try sz.float(2.5);
+ try std.testing.expectEqualStrings("2.5", buf.items);
+ buf.clearRetainingCapacity();
+
+ // Sized float
+ try sz.float(@as(f32, 2.5));
+ try std.testing.expectEqualStrings("2.5", buf.items);
+ buf.clearRetainingCapacity();
+}
+
+test "std.zon stringify vector" {
+ try expectSerializeEqual(
+ \\.{
+ \\ .{},
+ \\ .{
+ \\ true,
+ \\ false,
+ \\ true,
+ \\ },
+ \\ .{},
+ \\ .{
+ \\ 1.5,
+ \\ 2.5,
+ \\ 3.5,
+ \\ },
+ \\ .{},
+ \\ .{
+ \\ 2,
+ \\ 4,
+ \\ 6,
+ \\ },
+ \\ .{ 1, 2 },
+ \\ .{
+ \\ 3,
+ \\ 4,
+ \\ null,
+ \\ },
+ \\}
+ ,
+ .{
+ @Vector(0, bool){},
+ @Vector(3, bool){ true, false, true },
+ @Vector(0, f32){},
+ @Vector(3, f32){ 1.5, 2.5, 3.5 },
+ @Vector(0, u8){},
+ @Vector(3, u8){ 2, 4, 6 },
+ @Vector(2, *const u8){ &1, &2 },
+ @Vector(3, ?*const u8){ &3, &4, null },
+ },
+ .{},
+ );
+}
+
+test "std.zon pointers" {
+ // Primitive with varying levels of pointers
+ try expectSerializeEqual("10", &@as(u32, 10), .{});
+ try expectSerializeEqual("10", &&@as(u32, 10), .{});
+ try expectSerializeEqual("10", &&&@as(u32, 10), .{});
+
+ // Primitive optional with varying levels of pointers
+ try expectSerializeEqual("10", @as(?*const u32, &10), .{});
+ try expectSerializeEqual("null", @as(?*const u32, null), .{});
+ try expectSerializeEqual("10", @as(?*const u32, &10), .{});
+ try expectSerializeEqual("null", @as(*const ?u32, &null), .{});
+
+ try expectSerializeEqual("10", @as(?*const *const u32, &&10), .{});
+ try expectSerializeEqual("null", @as(?*const *const u32, null), .{});
+ try expectSerializeEqual("10", @as(*const ?*const u32, &&10), .{});
+ try expectSerializeEqual("null", @as(*const ?*const u32, &null), .{});
+ try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{});
+ try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{});
+
+ try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{});
+
+ // 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 val: ?*const Outer = &.{
+ .f1 = &&.{
+ .f1 = &null,
+ .f2 = &&"foo",
+ },
+ .f2 = &null,
+ };
+
+ try expectSerializeEqual(
+ \\.{ .f1 = .{ .f1 = null, .f2 = "foo" }, .f2 = null }
+ , val, .{});
+ }
+}