diff options
Diffstat (limited to 'lib/std')
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, .{}); + } +} |
