Commit 13c6eb0d71

Mason Remaley <mason@anthropicstudios.com>
2024-11-04 23:03:36
compiler,std: implement ZON support
This commit allows using ZON (Zig Object Notation) in a few ways. * `@import` can be used to load ZON at comptime and convert it to a normal Zig value. In this case, `@import` must have a result type. * `std.zon.parse` can be used to parse ZON at runtime, akin to the parsing logic in `std.json`. * `std.zon.stringify` can be used to convert arbitrary data structures to ZON at runtime, again akin to `std.json`.
1 parent 953355e
Changed files (145)
lib
src
test
behavior
cases
compile_errors
zon
src
lib/compiler/aro/aro/Value.zig
@@ -473,7 +473,7 @@ pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T {
     if (comp.interner.get(v.ref()) != .int) return null;
     var space: BigIntSpace = undefined;
     const big_int = v.toBigInt(&space, comp);
-    return big_int.to(T) catch null;
+    return big_int.toInt(T) catch null;
 }
 
 const ComplexOp = enum {
lib/compiler/aro/backend/Interner.zig
@@ -628,13 +628,13 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref {
                     if (data.fitsInTwosComp(.unsigned, 32)) {
                         i.items.appendAssumeCapacity(.{
                             .tag = .u32,
-                            .data = data.to(u32) catch unreachable,
+                            .data = data.toInt(u32) catch unreachable,
                         });
                         break :int;
                     } else if (data.fitsInTwosComp(.signed, 32)) {
                         i.items.appendAssumeCapacity(.{
                             .tag = .i32,
-                            .data = @bitCast(data.to(i32) catch unreachable),
+                            .data = @bitCast(data.toInt(i32) catch unreachable),
                         });
                         break :int;
                     }
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`.
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" {
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" {
lib/std/zig/AstGen.zig
@@ -9448,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;
@@ -11551,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(
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.
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,
@@ -1673,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,
@@ -3841,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 };
lib/std/zig/Zoir.zig
@@ -54,7 +54,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 +96,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].*;
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 {
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});
+    }
+}
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, .{});
+    }
+}
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");
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;
+}
src/codegen/llvm/Builder.zig
@@ -13776,8 +13776,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
                         };
                         const bit_count = extra.type.scalarBits(self);
                         const val: i64 = if (bit_count <= 64)
-                            bigint.to(i64) catch unreachable
-                        else if (bigint.to(u64)) |val|
+                            bigint.toInt(i64) catch unreachable
+                        else if (bigint.toInt(u64)) |val|
                             @bitCast(val)
                         else |_| {
                             const limbs = try record.addManyAsSlice(
@@ -14276,9 +14276,9 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
                                 else => unreachable,
                             },
                         };
-                        const val: i64 = if (bigint.to(i64)) |val|
+                        const val: i64 = if (bigint.toInt(i64)) |val|
                             val
-                        else |_| if (bigint.to(u64)) |val|
+                        else |_| if (bigint.toInt(u64)) |val|
                             @bitCast(val)
                         else |_| {
                             const limbs_len = std.math.divCeil(u32, extra.bit_width, 64) catch unreachable;
src/Sema/LowerZon.zig
@@ -0,0 +1,858 @@
+const std = @import("std");
+const Zcu = @import("../Zcu.zig");
+const Sema = @import("../Sema.zig");
+const Air = @import("../Air.zig");
+const InternPool = @import("../InternPool.zig");
+const Type = @import("../Type.zig");
+const Value = @import("../Value.zig");
+const Zir = std.zig.Zir;
+const AstGen = std.zig.AstGen;
+const CompileError = Zcu.CompileError;
+const Ast = std.zig.Ast;
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const File = Zcu.File;
+const LazySrcLoc = Zcu.LazySrcLoc;
+const Ref = std.zig.Zir.Inst.Ref;
+const NullTerminatedString = InternPool.NullTerminatedString;
+const NumberLiteralError = std.zig.number_literal.Error;
+const NodeIndex = std.zig.Ast.Node.Index;
+const Zoir = std.zig.Zoir;
+
+const LowerZon = @This();
+
+sema: *Sema,
+file: *File,
+file_index: Zcu.File.Index,
+import_loc: LazySrcLoc,
+block: *Sema.Block,
+base_node_inst: InternPool.TrackedInst.Index,
+
+/// Lowers the given file as ZON.
+pub fn run(
+    sema: *Sema,
+    file: *File,
+    file_index: Zcu.File.Index,
+    res_ty: Type,
+    import_loc: LazySrcLoc,
+    block: *Sema.Block,
+) CompileError!InternPool.Index {
+    const pt = sema.pt;
+
+    _ = try file.getZoir(pt.zcu);
+
+    const tracked_inst = try pt.zcu.intern_pool.trackZir(pt.zcu.gpa, pt.tid, .{
+        .file = file_index,
+        .inst = .main_struct_inst, // this is the only trackable instruction in a ZON file
+    });
+
+    var lower_zon: LowerZon = .{
+        .sema = sema,
+        .file = file,
+        .file_index = file_index,
+        .import_loc = import_loc,
+        .block = block,
+        .base_node_inst = tracked_inst,
+    };
+
+    try lower_zon.checkType(res_ty);
+
+    return lower_zon.lowerExpr(.root, res_ty);
+}
+
+/// Validate that `ty` is a valid ZON type. If not, emit a compile error.
+/// i.e. no nested optionals, no error sets, etc.
+fn checkType(self: *LowerZon, ty: Type) !void {
+    var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty;
+    try self.checkTypeInner(ty, null, &visited);
+}
+
+fn checkTypeInner(
+    self: *LowerZon,
+    ty: Type,
+    parent_opt_ty: ?Type,
+    /// Visited structs and unions (not tuples). These are tracked because they are the only way in
+    /// which a type can be self-referential, so must be tracked to avoid loops. Tracking more types
+    /// consumes memory unnecessarily, and would be complicated by optionals.
+    /// Allocated into `self.sema.arena`.
+    visited: *std.AutoHashMapUnmanaged(InternPool.Index, void),
+) !void {
+    const sema = self.sema;
+    const pt = sema.pt;
+    const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
+
+    switch (ty.zigTypeTag(zcu)) {
+        .bool,
+        .int,
+        .float,
+        .null,
+        .@"enum",
+        .comptime_float,
+        .comptime_int,
+        .enum_literal,
+        => {},
+
+        .noreturn,
+        .void,
+        .type,
+        .undefined,
+        .error_union,
+        .error_set,
+        .@"fn",
+        .frame,
+        .@"anyframe",
+        .@"opaque",
+        => return self.failUnsupportedResultType(ty, null),
+
+        .pointer => {
+            const ptr_info = ty.ptrInfo(zcu);
+            if (!ptr_info.flags.is_const) {
+                return self.failUnsupportedResultType(
+                    ty,
+                    "ZON does not allow mutable pointers",
+                );
+            }
+            switch (ptr_info.flags.size) {
+                .one => try self.checkTypeInner(
+                    .fromInterned(ptr_info.child),
+                    parent_opt_ty, // preserved
+                    visited,
+                ),
+                .slice => try self.checkTypeInner(
+                    .fromInterned(ptr_info.child),
+                    null,
+                    visited,
+                ),
+                .many => return self.failUnsupportedResultType(ty, "ZON does not allow many-pointers"),
+                .c => return self.failUnsupportedResultType(ty, "ZON does not allow C pointers"),
+            }
+        },
+        .optional => if (parent_opt_ty) |p| {
+            return self.failUnsupportedResultType(p, "ZON does not allow nested optionals");
+        } else try self.checkTypeInner(
+            ty.optionalChild(zcu),
+            ty,
+            visited,
+        ),
+        .array, .vector => {
+            try self.checkTypeInner(ty.childType(zcu), null, visited);
+        },
+        .@"struct" => if (ty.isTuple(zcu)) {
+            const tuple_info = ip.indexToKey(ty.toIntern()).tuple_type;
+            const field_types = tuple_info.types.get(ip);
+            for (field_types) |field_type| {
+                try self.checkTypeInner(.fromInterned(field_type), null, visited);
+            }
+        } else {
+            const gop = try visited.getOrPut(sema.arena, ty.toIntern());
+            if (gop.found_existing) return;
+            try ty.resolveFields(pt);
+            const struct_info = zcu.typeToStruct(ty).?;
+            for (struct_info.field_types.get(ip)) |field_type| {
+                try self.checkTypeInner(.fromInterned(field_type), null, visited);
+            }
+        },
+        .@"union" => {
+            const gop = try visited.getOrPut(sema.arena, ty.toIntern());
+            if (gop.found_existing) return;
+            try ty.resolveFields(pt);
+            const union_info = zcu.typeToUnion(ty).?;
+            for (union_info.field_types.get(ip)) |field_type| {
+                if (field_type != .void_type) {
+                    try self.checkTypeInner(.fromInterned(field_type), null, visited);
+                }
+            }
+        },
+    }
+}
+
+fn nodeSrc(self: *LowerZon, node: Zoir.Node.Index) LazySrcLoc {
+    return .{
+        .base_node_inst = self.base_node_inst,
+        .offset = .{ .node_abs = node.getAstNode(self.file.zoir.?) },
+    };
+}
+
+fn failUnsupportedResultType(
+    self: *LowerZon,
+    ty: Type,
+    opt_note: ?[]const u8,
+) error{ AnalysisFail, OutOfMemory } {
+    @branchHint(.cold);
+    const sema = self.sema;
+    const gpa = sema.gpa;
+    const pt = sema.pt;
+    return sema.failWithOwnedErrorMsg(self.block, msg: {
+        const msg = try sema.errMsg(self.import_loc, "type '{}' is not available in ZON", .{ty.fmt(pt)});
+        errdefer msg.destroy(gpa);
+        if (opt_note) |n| try sema.errNote(self.import_loc, msg, "{s}", .{n});
+        break :msg msg;
+    });
+}
+
+fn fail(
+    self: *LowerZon,
+    node: Zoir.Node.Index,
+    comptime format: []const u8,
+    args: anytype,
+) error{ AnalysisFail, OutOfMemory } {
+    @branchHint(.cold);
+    const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.nodeSrc(node), format, args);
+    try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{});
+    return self.sema.failWithOwnedErrorMsg(self.block, err_msg);
+}
+
+fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index {
+    const pt = self.sema.pt;
+    return self.lowerExprInner(node, res_ty) catch |err| switch (err) {
+        error.WrongType => return self.fail(
+            node,
+            "expected type '{}'",
+            .{res_ty.fmt(pt)},
+        ),
+        else => |e| return e,
+    };
+}
+
+fn lowerExprInner(
+    self: *LowerZon,
+    node: Zoir.Node.Index,
+    res_ty: Type,
+) (CompileError || error{WrongType})!InternPool.Index {
+    const pt = self.sema.pt;
+    switch (res_ty.zigTypeTag(pt.zcu)) {
+        .optional => return pt.intern(.{
+            .opt = .{
+                .ty = res_ty.toIntern(),
+                .val = if (node.get(self.file.zoir.?) == .null) b: {
+                    break :b .none;
+                } else b: {
+                    const child_type = res_ty.optionalChild(pt.zcu);
+                    break :b try self.lowerExprInner(node, child_type);
+                },
+            },
+        }),
+        .pointer => {
+            const ptr_info = res_ty.ptrInfo(pt.zcu);
+            switch (ptr_info.flags.size) {
+                .one => return pt.intern(.{ .ptr = .{
+                    .ty = res_ty.toIntern(),
+                    .base_addr = .{
+                        .uav = .{
+                            .orig_ty = res_ty.toIntern(),
+                            .val = try self.lowerExprInner(node, .fromInterned(ptr_info.child)),
+                        },
+                    },
+                    .byte_offset = 0,
+                } }),
+                .slice => return self.lowerSlice(node, res_ty),
+                else => {
+                    // Unsupported pointer type, checked in `lower`
+                    unreachable;
+                },
+            }
+        },
+        .bool => return self.lowerBool(node),
+        .int, .comptime_int => return self.lowerInt(node, res_ty),
+        .float, .comptime_float => return self.lowerFloat(node, res_ty),
+        .null => return self.lowerNull(node),
+        .@"enum" => return self.lowerEnum(node, res_ty),
+        .enum_literal => return self.lowerEnumLiteral(node),
+        .array => return self.lowerArray(node, res_ty),
+        .@"struct" => return self.lowerStructOrTuple(node, res_ty),
+        .@"union" => return self.lowerUnion(node, res_ty),
+        .vector => return self.lowerVector(node, res_ty),
+
+        .type,
+        .noreturn,
+        .undefined,
+        .error_union,
+        .error_set,
+        .@"fn",
+        .@"opaque",
+        .frame,
+        .@"anyframe",
+        .void,
+        => return self.fail(node, "type '{}' not available in ZON", .{res_ty.fmt(pt)}),
+    }
+}
+
+fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
+    return switch (node.get(self.file.zoir.?)) {
+        .true => .bool_true,
+        .false => .bool_false,
+        else => return error.WrongType,
+    };
+}
+
+fn lowerInt(
+    self: *LowerZon,
+    node: Zoir.Node.Index,
+    res_ty: Type,
+) !InternPool.Index {
+    @setFloatMode(.strict);
+    return switch (node.get(self.file.zoir.?)) {
+        .int_literal => |int| switch (int) {
+            .small => |val| {
+                const rhs: i32 = val;
+
+                // If our result is a fixed size integer, check that our value is not out of bounds
+                if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
+                    const lhs_info = res_ty.intInfo(self.sema.pt.zcu);
+
+                    // If lhs is unsigned and rhs is less than 0, we're out of bounds
+                    if (lhs_info.signedness == .unsigned and rhs < 0) return self.fail(
+                        node,
+                        "type '{}' cannot represent integer value '{}'",
+                        .{ res_ty.fmt(self.sema.pt), rhs },
+                    );
+
+                    // If lhs has less than the 32 bits rhs can hold, we need to check the max and
+                    // min values
+                    if (std.math.cast(u5, lhs_info.bits)) |bits| {
+                        const min_int: i32 = if (lhs_info.signedness == .unsigned or bits == 0) b: {
+                            break :b 0;
+                        } else b: {
+                            break :b -(@as(i32, 1) << (bits - 1));
+                        };
+                        const max_int: i32 = if (bits == 0) b: {
+                            break :b 0;
+                        } else b: {
+                            break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1;
+                        };
+                        if (rhs < min_int or rhs > max_int) {
+                            return self.fail(
+                                node,
+                                "type '{}' cannot represent integer value '{}'",
+                                .{ res_ty.fmt(self.sema.pt), rhs },
+                            );
+                        }
+                    }
+                }
+
+                return self.sema.pt.intern(.{ .int = .{
+                    .ty = res_ty.toIntern(),
+                    .storage = .{ .i64 = rhs },
+                } });
+            },
+            .big => |val| {
+                if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
+                    const int_info = res_ty.intInfo(self.sema.pt.zcu);
+                    if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) {
+                        return self.fail(
+                            node,
+                            "type '{}' cannot represent integer value '{}'",
+                            .{ res_ty.fmt(self.sema.pt), val },
+                        );
+                    }
+                }
+
+                return self.sema.pt.intern(.{ .int = .{
+                    .ty = res_ty.toIntern(),
+                    .storage = .{ .big_int = val },
+                } });
+            },
+        },
+        .float_literal => |val| {
+            // Check for fractional components
+            if (@rem(val, 1) != 0) {
+                return self.fail(
+                    node,
+                    "fractional component prevents float value '{}' from coercion to type '{}'",
+                    .{ val, res_ty.fmt(self.sema.pt) },
+                );
+            }
+
+            // Create a rational representation of the float
+            var rational = try std.math.big.Rational.init(self.sema.arena);
+            rational.setFloat(f128, val) catch |err| switch (err) {
+                error.NonFiniteFloat => unreachable,
+                error.OutOfMemory => return error.OutOfMemory,
+            };
+
+            // The float is reduced in rational.setFloat, so we assert that denominator is equal to
+            // one
+            const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true };
+            assert(rational.q.toConst().eqlAbs(big_one));
+
+            // Check that the result is in range of the result type
+            const int_info = res_ty.intInfo(self.sema.pt.zcu);
+            if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) {
+                return self.fail(
+                    node,
+                    "type '{}' cannot represent integer value '{}'",
+                    .{ val, res_ty.fmt(self.sema.pt) },
+                );
+            }
+
+            return self.sema.pt.intern(.{
+                .int = .{
+                    .ty = res_ty.toIntern(),
+                    .storage = .{ .big_int = rational.p.toConst() },
+                },
+            });
+        },
+        .char_literal => |val| {
+            // If our result is a fixed size integer, check that our value is not out of bounds
+            if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) {
+                const dest_info = res_ty.intInfo(self.sema.pt.zcu);
+                const unsigned_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed);
+                if (unsigned_bits < 21) {
+                    const out_of_range: u21 = @as(u21, 1) << @intCast(unsigned_bits);
+                    if (val >= out_of_range) {
+                        return self.fail(
+                            node,
+                            "type '{}' cannot represent integer value '{}'",
+                            .{ res_ty.fmt(self.sema.pt), val },
+                        );
+                    }
+                }
+            }
+            return self.sema.pt.intern(.{
+                .int = .{
+                    .ty = res_ty.toIntern(),
+                    .storage = .{ .i64 = val },
+                },
+            });
+        },
+
+        else => return error.WrongType,
+    };
+}
+
+fn lowerFloat(
+    self: *LowerZon,
+    node: Zoir.Node.Index,
+    res_ty: Type,
+) !InternPool.Index {
+    @setFloatMode(.strict);
+    const value = switch (node.get(self.file.zoir.?)) {
+        .int_literal => |int| switch (int) {
+            .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
+            .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)),
+        },
+        .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
+        .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
+        .pos_inf => b: {
+            if (res_ty.toIntern() == .comptime_float_type) return self.fail(
+                node,
+                "expected type '{}'",
+                .{res_ty.fmt(self.sema.pt)},
+            );
+            break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128));
+        },
+        .neg_inf => b: {
+            if (res_ty.toIntern() == .comptime_float_type) return self.fail(
+                node,
+                "expected type '{}'",
+                .{res_ty.fmt(self.sema.pt)},
+            );
+            break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128));
+        },
+        .nan => b: {
+            if (res_ty.toIntern() == .comptime_float_type) return self.fail(
+                node,
+                "expected type '{}'",
+                .{res_ty.fmt(self.sema.pt)},
+            );
+            break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128));
+        },
+        else => return error.WrongType,
+    };
+    return value.toIntern();
+}
+
+fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
+    switch (node.get(self.file.zoir.?)) {
+        .null => return .null_value,
+        else => return error.WrongType,
+    }
+}
+
+fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const array_info = res_ty.arrayInfo(self.sema.pt.zcu);
+    const nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
+        .array_literal => |nodes| nodes,
+        .empty_literal => .{ .start = node, .len = 0 },
+        else => return error.WrongType,
+    };
+
+    if (nodes.len != array_info.len) {
+        return error.WrongType;
+    }
+
+    const elems = try self.sema.arena.alloc(
+        InternPool.Index,
+        nodes.len + @intFromBool(array_info.sentinel != null),
+    );
+
+    for (0..nodes.len) |i| {
+        elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type);
+    }
+
+    if (array_info.sentinel) |sentinel| {
+        elems[elems.len - 1] = sentinel.toIntern();
+    }
+
+    return self.sema.pt.intern(.{ .aggregate = .{
+        .ty = res_ty.toIntern(),
+        .storage = .{ .elems = elems },
+    } });
+}
+
+fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    switch (node.get(self.file.zoir.?)) {
+        .enum_literal => |field_name| {
+            const field_name_interned = try ip.getOrPutString(
+                self.sema.gpa,
+                self.sema.pt.tid,
+                field_name.get(self.file.zoir.?),
+                .no_embedded_nulls,
+            );
+            const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse {
+                return self.fail(
+                    node,
+                    "enum {} has no member named '{}'",
+                    .{
+                        res_ty.fmt(self.sema.pt),
+                        std.zig.fmtId(field_name.get(self.file.zoir.?)),
+                    },
+                );
+            };
+
+            const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index);
+
+            return value.toIntern();
+        },
+        else => return error.WrongType,
+    }
+}
+
+fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    switch (node.get(self.file.zoir.?)) {
+        .enum_literal => |field_name| {
+            const field_name_interned = try ip.getOrPutString(
+                self.sema.gpa,
+                self.sema.pt.tid,
+                field_name.get(self.file.zoir.?),
+                .no_embedded_nulls,
+            );
+            return self.sema.pt.intern(.{ .enum_literal = field_name_interned });
+        },
+        else => return error.WrongType,
+    }
+}
+
+fn lowerStructOrTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    return switch (ip.indexToKey(res_ty.toIntern())) {
+        .tuple_type => self.lowerTuple(node, res_ty),
+        .struct_type => self.lowerStruct(node, res_ty),
+        else => unreachable,
+    };
+}
+
+fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+
+    const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type;
+
+    const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
+        .array_literal => |nodes| nodes,
+        .empty_literal => .{ .start = node, .len = 0 },
+        else => return error.WrongType,
+    };
+
+    const field_types = tuple_info.types.get(ip);
+    const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len);
+
+    const field_comptime_vals = tuple_info.values.get(ip);
+    if (field_comptime_vals.len > 0) {
+        @memcpy(elems, field_comptime_vals);
+    } else {
+        @memset(elems, .none);
+    }
+
+    for (0..elem_nodes.len) |i| {
+        if (i >= elems.len) {
+            const elem_node = elem_nodes.at(@intCast(i));
+            return self.fail(
+                elem_node,
+                "index {} outside tuple of length {}",
+                .{
+                    elems.len,
+                    elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?),
+                },
+            );
+        }
+
+        const val = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i]));
+
+        if (elems[i] != .none and val != elems[i]) {
+            const elem_node = elem_nodes.at(@intCast(i));
+            return self.fail(
+                elem_node,
+                "value stored in comptime field does not match the default value of the field",
+                .{},
+            );
+        }
+
+        elems[i] = val;
+    }
+
+    for (elems, 0..) |val, i| {
+        if (val == .none) {
+            return self.fail(node, "missing tuple field with index {}", .{i});
+        }
+    }
+
+    return self.sema.pt.intern(.{ .aggregate = .{
+        .ty = res_ty.toIntern(),
+        .storage = .{ .elems = elems },
+    } });
+}
+
+fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    const gpa = self.sema.gpa;
+
+    try res_ty.resolveFields(self.sema.pt);
+    try res_ty.resolveStructFieldInits(self.sema.pt);
+    const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?;
+
+    const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) {
+        .struct_literal => |fields| fields,
+        .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
+        else => return error.WrongType,
+    };
+
+    const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len);
+
+    const field_defaults = struct_info.field_inits.get(ip);
+    if (field_defaults.len > 0) {
+        @memcpy(field_values, field_defaults);
+    } else {
+        @memset(field_values, .none);
+    }
+
+    for (0..fields.names.len) |i| {
+        const field_name = try ip.getOrPutString(
+            gpa,
+            self.sema.pt.tid,
+            fields.names[i].get(self.file.zoir.?),
+            .no_embedded_nulls,
+        );
+        const field_node = fields.vals.at(@intCast(i));
+
+        const name_index = struct_info.nameIndex(ip, field_name) orelse {
+            return self.fail(field_node, "unexpected field '{}'", .{field_name.fmt(ip)});
+        };
+
+        const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]);
+        field_values[name_index] = try self.lowerExpr(field_node, field_type);
+
+        if (struct_info.comptime_bits.getBit(ip, name_index)) {
+            const val = ip.indexToKey(field_values[name_index]);
+            const default = ip.indexToKey(field_defaults[name_index]);
+            if (!val.eql(default, ip)) {
+                return self.fail(
+                    field_node,
+                    "value stored in comptime field does not match the default value of the field",
+                    .{},
+                );
+            }
+        }
+    }
+
+    const field_names = struct_info.field_names.get(ip);
+    for (field_values, field_names) |*value, name| {
+        if (value.* == .none) return self.fail(node, "missing field '{}'", .{name.fmt(ip)});
+    }
+
+    return self.sema.pt.intern(.{ .aggregate = .{
+        .ty = res_ty.toIntern(),
+        .storage = .{
+            .elems = field_values,
+        },
+    } });
+}
+
+fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    const gpa = self.sema.gpa;
+
+    const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu);
+
+    assert(ptr_info.flags.size == .slice);
+
+    // String literals
+    const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1";
+    const string_sentinel = ptr_info.sentinel == .none or ptr_info.sentinel == .zero_u8;
+    if (string_alignment and ptr_info.child == .u8_type and string_sentinel) {
+        switch (node.get(self.file.zoir.?)) {
+            .string_literal => |val| {
+                const ip_str = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls);
+                const str_ref = try self.sema.addStrLit(ip_str, val.len);
+                return (try self.sema.coerce(
+                    self.block,
+                    res_ty,
+                    str_ref,
+                    self.nodeSrc(node),
+                )).toInterned().?;
+            },
+            else => {},
+        }
+    }
+
+    // Slice literals
+    const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
+        .array_literal => |nodes| nodes,
+        .empty_literal => .{ .start = node, .len = 0 },
+        else => return error.WrongType,
+    };
+
+    const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none));
+
+    for (elems, 0..) |*elem, i| {
+        elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child));
+    }
+
+    if (ptr_info.sentinel != .none) {
+        elems[elems.len - 1] = ptr_info.sentinel;
+    }
+
+    const array_ty = try self.sema.pt.intern(.{ .array_type = .{
+        .len = elems.len,
+        .sentinel = ptr_info.sentinel,
+        .child = ptr_info.child,
+    } });
+
+    const array = try self.sema.pt.intern(.{ .aggregate = .{
+        .ty = array_ty,
+        .storage = .{ .elems = elems },
+    } });
+
+    const many_item_ptr_type = try self.sema.pt.intern(.{ .ptr_type = .{
+        .child = ptr_info.child,
+        .sentinel = ptr_info.sentinel,
+        .flags = b: {
+            var flags = ptr_info.flags;
+            flags.size = .many;
+            break :b flags;
+        },
+        .packed_offset = ptr_info.packed_offset,
+    } });
+
+    const many_item_ptr = try self.sema.pt.intern(.{
+        .ptr = .{
+            .ty = many_item_ptr_type,
+            .base_addr = .{
+                .uav = .{
+                    .orig_ty = (try self.sema.pt.singleConstPtrType(.fromInterned(array_ty))).toIntern(),
+                    .val = array,
+                },
+            },
+            .byte_offset = 0,
+        },
+    });
+
+    const len = (try self.sema.pt.intValue(.usize, elems.len)).toIntern();
+
+    return self.sema.pt.intern(.{ .slice = .{
+        .ty = res_ty.toIntern(),
+        .ptr = many_item_ptr,
+        .len = len,
+    } });
+}
+
+fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+    try res_ty.resolveFields(self.sema.pt);
+    const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?;
+    const enum_tag_info = union_info.loadTagType(ip);
+
+    const field_name, const maybe_field_node = switch (node.get(self.file.zoir.?)) {
+        .enum_literal => |name| b: {
+            const field_name = try ip.getOrPutString(
+                self.sema.gpa,
+                self.sema.pt.tid,
+                name.get(self.file.zoir.?),
+                .no_embedded_nulls,
+            );
+            break :b .{ field_name, null };
+        },
+        .struct_literal => b: {
+            const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) {
+                .struct_literal => |fields| fields,
+                else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}),
+            };
+            if (fields.names.len != 1) {
+                return error.WrongType;
+            }
+            const field_name = try ip.getOrPutString(
+                self.sema.gpa,
+                self.sema.pt.tid,
+                fields.names[0].get(self.file.zoir.?),
+                .no_embedded_nulls,
+            );
+            break :b .{ field_name, fields.vals.at(0) };
+        },
+        else => return error.WrongType,
+    };
+
+    const name_index = enum_tag_info.nameIndex(ip, field_name) orelse {
+        return error.WrongType;
+    };
+    const tag = try self.sema.pt.enumValueFieldIndex(.fromInterned(union_info.enum_tag_ty), name_index);
+    const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]);
+    const val = if (maybe_field_node) |field_node| b: {
+        if (field_type.toIntern() == .void_type) {
+            return self.fail(field_node, "expected type 'void'", .{});
+        }
+        break :b try self.lowerExpr(field_node, field_type);
+    } else b: {
+        if (field_type.toIntern() != .void_type) {
+            return error.WrongType;
+        }
+        break :b .void_value;
+    };
+    return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{
+        .ty = res_ty.toIntern(),
+        .tag = tag.toIntern(),
+        .val = val,
+    });
+}
+
+fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index {
+    const ip = &self.sema.pt.zcu.intern_pool;
+
+    const vector_info = ip.indexToKey(res_ty.toIntern()).vector_type;
+
+    const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) {
+        .array_literal => |nodes| nodes,
+        .empty_literal => .{ .start = node, .len = 0 },
+        else => return error.WrongType,
+    };
+
+    const elems = try self.sema.arena.alloc(InternPool.Index, vector_info.len);
+
+    if (elem_nodes.len != vector_info.len) {
+        return self.fail(
+            node,
+            "expected {} vector elements; found {}",
+            .{ vector_info.len, elem_nodes.len },
+        );
+    }
+
+    for (elems, 0..) |*elem, i| {
+        elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child));
+    }
+
+    return self.sema.pt.intern(.{ .aggregate = .{
+        .ty = res_ty.toIntern(),
+        .storage = .{ .elems = elems },
+    } });
+}
src/Zcu/PerThread.zig
@@ -1867,6 +1867,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const file = zcu.fileByIndex(file_index);
+    assert(file.getMode() == .zig);
     assert(zcu.fileRootType(file_index) == .none);
 
     if (file.status != .success_zir) {
@@ -2022,7 +2023,9 @@ pub fn importFile(
     if (mod.deps.get(import_string)) |pkg| {
         return pt.importPkg(pkg);
     }
-    if (!std.mem.endsWith(u8, import_string, ".zig")) {
+    if (!std.mem.endsWith(u8, import_string, ".zig") and
+        !std.mem.endsWith(u8, import_string, ".zon"))
+    {
         return error.ModuleNotFound;
     }
     const gpa = zcu.gpa;
src/Compilation.zig
@@ -2220,7 +2220,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
         try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count());
         for (zcu.import_table.values()) |file_index| {
             if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
-            comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
+            const file = zcu.fileByIndex(file_index);
+            if (file.getMode() == .zig) {
+                comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
+            }
         }
         if (comp.file_system_inputs) |fsi| {
             for (zcu.import_table.values()) |file_index| {
@@ -3206,10 +3209,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
             if (error_msg) |msg| {
                 try addModuleErrorMsg(zcu, &bundle, msg.*);
             } else {
-                // Must be ZIR errors. Note that this may include AST errors.
-                // addZirErrorMessages asserts that the tree is loaded.
-                _ = try file.getTree(gpa);
-                try addZirErrorMessages(&bundle, file);
+                // Must be ZIR or Zoir errors. Note that this may include AST errors.
+                _ = try file.getTree(gpa); // Tree must be loaded.
+                if (file.zir_loaded) {
+                    try addZirErrorMessages(&bundle, file);
+                } else if (file.zoir != null) {
+                    try addZoirErrorMessages(&bundle, file);
+                } else {
+                    // Either Zir or Zoir must have been loaded.
+                    unreachable;
+                }
             }
         }
         var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: {
@@ -3623,6 +3632,15 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
     return eb.addZirErrorMessages(file.zir, file.tree, file.source, src_path);
 }
 
+pub fn addZoirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void {
+    assert(file.source_loaded);
+    assert(file.tree_loaded);
+    const gpa = eb.gpa;
+    const src_path = try file.fullPath(gpa);
+    defer gpa.free(src_path);
+    return eb.addZoirErrorMessages(file.zoir.?, file.tree, file.source, src_path);
+}
+
 pub fn performAllTheWork(
     comp: *Compilation,
     main_progress_node: std.Progress.Node,
@@ -4272,6 +4290,7 @@ fn workerAstGenFile(
     wg: *WaitGroup,
     src: Zcu.AstGenSrc,
 ) void {
+    assert(file.getMode() == .zig);
     const child_prog_node = prog_node.start(file.sub_file_path, 0);
     defer child_prog_node.end();
 
@@ -4325,7 +4344,7 @@ fn workerAstGenFile(
                 const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
                 break :blk .{ res, imported_path_digest };
             };
-            if (import_result.is_new) {
+            if (import_result.is_new and import_result.file.getMode() == .zig) {
                 log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
                     file.sub_file_path, import_path, import_result.file.sub_file_path,
                 });
src/fmt.zig
@@ -120,7 +120,7 @@ pub fn run(
                     process.exit(2);
                 }
             } else {
-                const zoir = try std.zig.ZonGen.generate(gpa, tree);
+                const zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
                 defer zoir.deinit(gpa);
 
                 if (zoir.hasCompileErrors()) {
@@ -335,7 +335,7 @@ fn fmtPathFile(
                 }
             },
             .zon => {
-                var zoir = try std.zig.ZonGen.generate(gpa, tree);
+                var zoir = try std.zig.ZonGen.generate(gpa, tree, .{});
                 defer zoir.deinit(gpa);
 
                 if (zoir.hasCompileErrors()) {
src/InternPool.zig
@@ -4389,7 +4389,7 @@ pub const LoadedEnumType = struct {
         // Auto-numbered enum. Convert `int_tag_val` to field index.
         const field_index = switch (ip.indexToKey(int_tag_val).int.storage) {
             inline .u64, .i64 => |x| std.math.cast(u32, x) orelse return null,
-            .big_int => |x| x.to(u32) catch return null,
+            .big_int => |x| x.toInt(u32) catch return null,
             .lazy_align, .lazy_size => unreachable,
         };
         return if (field_index < self.names.len) field_index else null;
@@ -7957,7 +7957,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                     .big_int => |big_int| {
                         items.appendAssumeCapacity(.{
                             .tag = .int_u8,
-                            .data = big_int.to(u8) catch unreachable,
+                            .data = big_int.toInt(u8) catch unreachable,
                         });
                         break :b;
                     },
@@ -7974,7 +7974,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                     .big_int => |big_int| {
                         items.appendAssumeCapacity(.{
                             .tag = .int_u16,
-                            .data = big_int.to(u16) catch unreachable,
+                            .data = big_int.toInt(u16) catch unreachable,
                         });
                         break :b;
                     },
@@ -7991,7 +7991,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                     .big_int => |big_int| {
                         items.appendAssumeCapacity(.{
                             .tag = .int_u32,
-                            .data = big_int.to(u32) catch unreachable,
+                            .data = big_int.toInt(u32) catch unreachable,
                         });
                         break :b;
                     },
@@ -8006,7 +8006,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                 },
                 .i32_type => switch (int.storage) {
                     .big_int => |big_int| {
-                        const casted = big_int.to(i32) catch unreachable;
+                        const casted = big_int.toInt(i32) catch unreachable;
                         items.appendAssumeCapacity(.{
                             .tag = .int_i32,
                             .data = @as(u32, @bitCast(casted)),
@@ -8024,7 +8024,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                 },
                 .usize_type => switch (int.storage) {
                     .big_int => |big_int| {
-                        if (big_int.to(u32)) |casted| {
+                        if (big_int.toInt(u32)) |casted| {
                             items.appendAssumeCapacity(.{
                                 .tag = .int_usize,
                                 .data = casted,
@@ -8045,14 +8045,14 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                 },
                 .comptime_int_type => switch (int.storage) {
                     .big_int => |big_int| {
-                        if (big_int.to(u32)) |casted| {
+                        if (big_int.toInt(u32)) |casted| {
                             items.appendAssumeCapacity(.{
                                 .tag = .int_comptime_int_u32,
                                 .data = casted,
                             });
                             break :b;
                         } else |_| {}
-                        if (big_int.to(i32)) |casted| {
+                        if (big_int.toInt(i32)) |casted| {
                             items.appendAssumeCapacity(.{
                                 .tag = .int_comptime_int_i32,
                                 .data = @as(u32, @bitCast(casted)),
@@ -8082,7 +8082,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
             }
             switch (int.storage) {
                 .big_int => |big_int| {
-                    if (big_int.to(u32)) |casted| {
+                    if (big_int.toInt(u32)) |casted| {
                         items.appendAssumeCapacity(.{
                             .tag = .int_small,
                             .data = try addExtra(extra, IntSmall{
src/main.zig
@@ -6278,7 +6278,7 @@ fn cmdAstCheck(
             }
         },
         .zon => {
-            const zoir = try ZonGen.generate(gpa, file.tree);
+            const zoir = try ZonGen.generate(gpa, file.tree, .{});
             defer zoir.deinit(gpa);
 
             if (zoir.hasCompileErrors()) {
src/print_zir.zig
@@ -488,7 +488,6 @@ const Writer = struct {
             .enum_literal,
             .decl_ref,
             .decl_val,
-            .import,
             .ret_err_value,
             .ret_err_value_code,
             .param_anytype,
@@ -515,6 +514,8 @@ const Writer = struct {
             .declaration => try self.writeDeclaration(stream, inst),
 
             .extended => try self.writeExtended(stream, inst),
+
+            .import => try self.writeImport(stream, inst),
         }
     }
 
@@ -2842,4 +2843,13 @@ const Writer = struct {
             try stream.writeByte('\n');
         }
     }
+
+    fn writeImport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
+        const extra = self.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
+        try self.writeInstRef(stream, extra.res_ty);
+        const import_path = self.code.nullTerminatedString(extra.path);
+        try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(import_path)});
+        try self.writeSrcTok(stream, inst_data.src_tok);
+    }
 };
src/Sema.zig
@@ -187,6 +187,7 @@ const Alignment = InternPool.Alignment;
 const AnalUnit = InternPool.AnalUnit;
 const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
 const Cache = std.Build.Cache;
+const LowerZon = @import("Sema/LowerZon.zig");
 
 pub const default_branch_quota = 1000;
 pub const default_reference_trace_len = 2;
@@ -5790,7 +5791,7 @@ fn addNullTerminatedStrLit(sema: *Sema, string: InternPool.NullTerminatedString)
     return sema.addStrLit(string.toString(), string.length(&sema.pt.zcu.intern_pool));
 }
 
-fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref {
+pub fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref {
     const pt = sema.pt;
     const array_ty = try pt.arrayType(.{
         .len = len,
@@ -13964,9 +13965,10 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
 
     const pt = sema.pt;
     const zcu = pt.zcu;
-    const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
+    const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
+    const extra = sema.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
     const operand_src = block.tokenOffset(inst_data.src_tok);
-    const operand = inst_data.get(sema.code);
+    const operand = sema.code.nullTerminatedString(extra.path);
 
     const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) {
         error.ImportOutsideModulePath => {
@@ -13983,12 +13985,42 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
         },
     };
-    try sema.declareDependency(.{ .file = result.file_index });
-    try pt.ensureFileAnalyzed(result.file_index);
-    const ty = zcu.fileRootType(result.file_index);
-    try sema.declareDependency(.{ .interned = ty });
-    try sema.addTypeReferenceEntry(operand_src, ty);
-    return Air.internedToRef(ty);
+    switch (result.file.getMode()) {
+        .zig => {
+            try sema.declareDependency(.{ .file = result.file_index });
+            try pt.ensureFileAnalyzed(result.file_index);
+            const ty = zcu.fileRootType(result.file_index);
+            try sema.declareDependency(.{ .interned = ty });
+            try sema.addTypeReferenceEntry(operand_src, ty);
+            return Air.internedToRef(ty);
+        },
+        .zon => {
+            _ = result.file.getTree(zcu.gpa) catch |err| {
+                // TODO: these errors are file system errors; make sure an update() will
+                // retry this and not cache the file system error, which may be transient.
+                return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ result.file.sub_file_path, @errorName(err) });
+            };
+
+            if (extra.res_ty == .none) {
+                return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{});
+            }
+            const res_ty_inst = try sema.resolveInst(extra.res_ty);
+            const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst);
+            if (res_ty.isGenericPoison()) {
+                return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{});
+            }
+
+            const interned = try LowerZon.run(
+                sema,
+                result.file,
+                result.file_index,
+                res_ty,
+                operand_src,
+                block,
+            );
+            return Air.internedToRef(interned);
+        },
+    }
 }
 
 fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
src/Value.zig
@@ -270,7 +270,7 @@ pub fn getUnsignedIntInner(
         else => switch (zcu.intern_pool.indexToKey(val.toIntern())) {
             .undef => unreachable,
             .int => |int| switch (int.storage) {
-                .big_int => |big_int| big_int.to(u64) catch null,
+                .big_int => |big_int| big_int.toInt(u64) catch null,
                 .u64 => |x| x,
                 .i64 => |x| std.math.cast(u64, x),
                 .lazy_align => |ty| (try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), zcu, tid)).scalar.toByteUnits() orelse 0,
@@ -311,7 +311,7 @@ pub fn toSignedInt(val: Value, zcu: *const Zcu) i64 {
         .bool_true => 1,
         else => switch (zcu.intern_pool.indexToKey(val.toIntern())) {
             .int => |int| switch (int.storage) {
-                .big_int => |big_int| big_int.to(i64) catch unreachable,
+                .big_int => |big_int| big_int.toInt(i64) catch unreachable,
                 .i64 => |x| x,
                 .u64 => |x| @intCast(x),
                 .lazy_align => |ty| @intCast(Type.fromInterned(ty).abiAlignment(zcu).toByteUnits() orelse 0),
@@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
 pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T {
     return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
         .int => |int| switch (int.storage) {
-            .big_int => |big_int| @floatCast(bigIntToFloat(big_int.limbs, big_int.positive)),
+            .big_int => |big_int| big_int.toFloat(T),
             inline .u64, .i64 => |x| {
                 if (T == f80) {
                     @panic("TODO we can't lower this properly on non-x86 llvm backend yet");
@@ -915,25 +915,6 @@ pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T {
     };
 }
 
-/// TODO move this to std lib big int code
-fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 {
-    if (limbs.len == 0) return 0;
-
-    const base = std.math.maxInt(std.math.big.Limb) + 1;
-    var result: f128 = 0;
-    var i: usize = limbs.len;
-    while (i != 0) {
-        i -= 1;
-        const limb: f128 = @floatFromInt(limbs[i]);
-        result = @mulAdd(f128, base, result, limb);
-    }
-    if (positive) {
-        return result;
-    } else {
-        return -result;
-    }
-}
-
 pub fn clz(val: Value, ty: Type, zcu: *Zcu) u64 {
     var bigint_buf: BigIntSpace = undefined;
     const bigint = val.toBigInt(&bigint_buf, zcu);
@@ -1548,7 +1529,7 @@ pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptim
         .undef => try pt.undefValue(float_ty),
         .int => |int| switch (int.storage) {
             .big_int => |big_int| {
-                const float = bigIntToFloat(big_int.limbs, big_int.positive);
+                const float = big_int.toFloat(f128);
                 return pt.floatValue(float_ty, float);
             },
             inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
@@ -4583,7 +4564,7 @@ pub fn interpret(val: Value, comptime T: type, pt: Zcu.PerThread) error{ OutOfMe
         .int => switch (ip.indexToKey(val.toIntern()).int.storage) {
             .lazy_align, .lazy_size => unreachable, // `val` is fully resolved
             inline .u64, .i64 => |x| std.math.cast(T, x) orelse return error.TypeMismatch,
-            .big_int => |big| big.to(T) catch return error.TypeMismatch,
+            .big_int => |big| big.toInt(T) catch return error.TypeMismatch,
         },
 
         .float => val.toFloat(T, zcu),
src/Zcu.zig
@@ -39,6 +39,8 @@ const AnalUnit = InternPool.AnalUnit;
 const BuiltinFn = std.zig.BuiltinFn;
 const LlvmObject = @import("codegen/llvm.zig").Object;
 const dev = @import("dev.zig");
+const Zoir = std.zig.Zoir;
+const ZonGen = std.zig.ZonGen;
 
 comptime {
     @setEvalBranchQuota(4000);
@@ -672,6 +674,8 @@ pub const File = struct {
     tree: Ast,
     /// Whether this is populated or not depends on `zir_loaded`.
     zir: Zir,
+    /// Cached Zoir, generated lazily.
+    zoir: ?Zoir = null,
     /// Module that this file is a part of, managed externally.
     mod: *Package.Module,
     /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen.
@@ -704,7 +708,19 @@ pub const File = struct {
         root: *Package.Module,
     };
 
+    pub fn getMode(self: File) Ast.Mode {
+        if (std.mem.endsWith(u8, self.sub_file_path, ".zon")) {
+            return .zon;
+        } else if (std.mem.endsWith(u8, self.sub_file_path, ".zig")) {
+            return .zig;
+        } else {
+            // `Module.importFile` rejects all other extensions
+            unreachable;
+        }
+    }
+
     pub fn unload(file: *File, gpa: Allocator) void {
+        if (file.zoir) |zoir| zoir.deinit(gpa);
         file.unloadTree(gpa);
         file.unloadSource(gpa);
         file.unloadZir(gpa);
@@ -778,11 +794,24 @@ pub const File = struct {
         if (file.tree_loaded) return &file.tree;
 
         const source = try file.getSource(gpa);
-        file.tree = try Ast.parse(gpa, source.bytes, .zig);
+        file.tree = try Ast.parse(gpa, source.bytes, file.getMode());
         file.tree_loaded = true;
         return &file.tree;
     }
 
+    pub fn getZoir(file: *File, zcu: *Zcu) !*const Zoir {
+        if (file.zoir) |*zoir| return zoir;
+
+        assert(file.tree_loaded);
+        assert(file.tree.mode == .zon);
+        file.zoir = try ZonGen.generate(zcu.gpa, file.tree, .{});
+        if (file.zoir.?.hasCompileErrors()) {
+            try zcu.failed_files.putNoClobber(zcu.gpa, file, null);
+            return error.AnalysisFail;
+        }
+        return &file.zoir.?;
+    }
+
     pub fn fullyQualifiedNameLen(file: File) usize {
         const ext = std.fs.path.extension(file.sub_file_path);
         return file.sub_file_path.len - ext.len;
@@ -895,6 +924,7 @@ pub const File = struct {
     pub const Index = InternPool.FileIndex;
 };
 
+/// Represents the contents of a file loaded with `@embedFile`.
 pub const EmbedFile = struct {
     /// Module that this file is a part of, managed externally.
     owner: *Package.Module,
@@ -2372,6 +2402,12 @@ pub const LazySrcLoc = struct {
             break :inst .{ info.file, info.inst };
         };
         const file = zcu.fileByIndex(file_index);
+
+        // If we're relative to .main_struct_inst, we know the ast node is the root and don't need to resolve the ZIR,
+        // which may not exist e.g. in the case of errors in ZON files.
+        if (zir_inst == .main_struct_inst) return .{ file, 0 };
+
+        // Otherwise, make sure ZIR is loaded.
         assert(file.zir_loaded);
 
         const zir = file.zir;
@@ -3461,8 +3497,6 @@ pub fn atomicPtrAlignment(
 }
 
 /// Returns null in the following cases:
-/// * `@TypeOf(.{})`
-/// * A struct which has no fields (`struct {}`).
 /// * Not a struct.
 pub fn typeToStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType {
     if (ty.ip_index == .none) return null;
test/behavior/zon/a.zon
@@ -0,0 +1,1 @@
+'a'
test/behavior/zon/abc-escaped.zon
@@ -0,0 +1,1 @@
+"ab\\c"
test/behavior/zon/abc.zon
@@ -0,0 +1,1 @@
+"abc"
test/behavior/zon/array.zon
@@ -0,0 +1,1 @@
+.{ 'a', 'b', 'c', 'd' }
test/behavior/zon/complex.zon
@@ -0,0 +1,7 @@
+.{
+    .f1 = .{
+        .f1 = null,
+        .f2 = "foo",
+    },
+    .f2 = null,
+}
test/behavior/zon/enum_field.zon
@@ -0,0 +1,1 @@
+.{ .x = .z }
test/behavior/zon/escaped_enum.zon
@@ -0,0 +1,1 @@
+.@"0\na"
test/behavior/zon/escaped_struct.zon
@@ -0,0 +1,2 @@
+
+.{ .@"0" = 1.5, .@"foo" = 2 }
test/behavior/zon/false.zon
@@ -0,0 +1,4 @@
+// Comment
+false // Another comment
+// Yet another comment
+
test/behavior/zon/floats.zon
@@ -0,0 +1,25 @@
+.{
+    0.5,
+    123.456,
+    -123.456,
+    42.5,
+
+    5.0,
+    5,
+    -102.0,
+    -102,
+
+    'a',
+    'z',
+
+    36893488147419103231,
+    -36893488147419103231,
+    0x1ffffffffffffffff,
+    0x1ffffffffffffffff,
+
+    12_3.0E+77,
+
+    0x103.70p-5,
+    -0x103.70,
+    0x1234_5678.9ABC_CDEFp-10,
+}
test/behavior/zon/foo.zon
@@ -0,0 +1,1 @@
+.foo
test/behavior/zon/inf_and_nan.zon
@@ -0,0 +1,5 @@
+.{
+    nan,
+    inf,
+    -inf,
+}
test/behavior/zon/ints.zon
@@ -0,0 +1,40 @@
+.{
+    10,
+    24,
+    -4,
+    -123,
+
+    127,
+    -128,
+
+    'a',
+    'z',
+
+    36893488147419103231,
+    368934_881_474191032_31,
+    -18446744073709551615,
+    -9223372036854775809,
+
+    36893488147419103231,
+    -36893488147419103232,
+
+    -1.0,
+    123.0,
+
+    0xff,
+    -0xff,
+    0o77,
+    -0o77,
+    0b11,
+    -0b11,
+
+    0x1ffffffffffffffff,
+    0x1ffffffffffffffff,
+    -0x1ffffffffffffffff,
+    0o3777777777777777777777,
+    0o3777777777777777777777,
+    -0o3777777777777777777777,
+    0b11111111111111111111111111111111111111111111111111111111111111111,
+    0b11111111111111111111111111111111111111111111111111111111111111111,
+    -0b11111111111111111111111111111111111111111111111111111111111111111,
+}
test/behavior/zon/multiline_string.zon
@@ -0,0 +1,4 @@
+// zig fmt: off
+    \\Hello, world!
+\\This is a multiline string!
+  \\ There are no escapes, we can, for example, include \n in the string
test/behavior/zon/none.zon
@@ -0,0 +1,1 @@
+null
test/behavior/zon/recursive.zon
@@ -0,0 +1,1 @@
+.{ .foo = .{ .foo = null } }
test/behavior/zon/slice-abc.zon
@@ -0,0 +1,1 @@
+.{'a', 'b', 'c'}
\ No newline at end of file
test/behavior/zon/slice-empty.zon
@@ -0,0 +1,1 @@
+.{}
\ No newline at end of file
test/behavior/zon/slice1_no_newline.zon
@@ -0,0 +1,1 @@
+.{ 1 }
\ No newline at end of file
test/behavior/zon/some.zon
@@ -0,0 +1,1 @@
+10
test/behavior/zon/string_embedded_null.zon
@@ -0,0 +1,1 @@
+"a\nb\x00c"
test/behavior/zon/true.zon
@@ -0,0 +1,1 @@
+true
test/behavior/zon/tuple.zon
@@ -0,0 +1,1 @@
+.{ 1.2, true, "hello", 3 }
test/behavior/zon/union1.zon
@@ -0,0 +1,1 @@
+.{ .x = 1.5 }
test/behavior/zon/union2.zon
@@ -0,0 +1,1 @@
+.{ .y = true }
test/behavior/zon/union3.zon
@@ -0,0 +1,1 @@
+.z
test/behavior/zon/vec0.zon
@@ -0,0 +1,1 @@
+.{}
test/behavior/zon/vec1.zon
@@ -0,0 +1,1 @@
+.{ .x = 1.5 }
test/behavior/zon/vec2.zon
@@ -0,0 +1,1 @@
+.{ .x = 1.5, .y = 2 }
test/behavior/zon/vec3_bool.zon
@@ -0,0 +1,1 @@
+.{ false, false, true }
test/behavior/zon/vec3_float.zon
@@ -0,0 +1,1 @@
+.{ 1.5, 2.5, 3.5 }
test/behavior/zon/vec3_int.zon
@@ -0,0 +1,1 @@
+.{ 2, 4, 6 }
test/behavior/zon/vec3_int_opt.zon
@@ -0,0 +1,1 @@
+.{ 2, null, 6 }
test/behavior/zon/z.zon
@@ -0,0 +1,1 @@
+'z'
test/behavior/zon.zig
@@ -0,0 +1,519 @@
+const std = @import("std");
+
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualDeep = std.testing.expectEqualDeep;
+const expectEqualSlices = std.testing.expectEqualSlices;
+const expectEqualStrings = std.testing.expectEqualStrings;
+
+test "bool" {
+    try expectEqual(true, @as(bool, @import("zon/true.zon")));
+    try expectEqual(false, @as(bool, @import("zon/false.zon")));
+}
+
+test "optional" {
+    const some: ?u32 = @import("zon/some.zon");
+    const none: ?u32 = @import("zon/none.zon");
+    const @"null": @TypeOf(null) = @import("zon/none.zon");
+    try expectEqual(@as(u32, 10), some);
+    try expectEqual(@as(?u32, null), none);
+    try expectEqual(null, @"null");
+}
+
+test "union" {
+    // No tag
+    {
+        const Union = union {
+            x: f32,
+            y: bool,
+            z: void,
+        };
+
+        const union1: Union = @import("zon/union1.zon");
+        const union2: Union = @import("zon/union2.zon");
+        const union3: Union = @import("zon/union3.zon");
+
+        try expectEqual(1.5, union1.x);
+        try expectEqual(true, union2.y);
+        try expectEqual({}, union3.z);
+    }
+
+    // Inferred tag
+    {
+        const Union = union(enum) {
+            x: f32,
+            y: bool,
+            z: void,
+        };
+
+        const union1: Union = comptime @import("zon/union1.zon");
+        const union2: Union = @import("zon/union2.zon");
+        const union3: Union = @import("zon/union3.zon");
+
+        try expectEqual(1.5, union1.x);
+        try expectEqual(true, union2.y);
+        try expectEqual({}, union3.z);
+    }
+
+    // Explicit tag
+    {
+        const Tag = enum(i128) {
+            x = -1,
+            y = 2,
+            z = 1,
+        };
+        const Union = union(Tag) {
+            x: f32,
+            y: bool,
+            z: void,
+        };
+
+        const union1: Union = @import("zon/union1.zon");
+        const union2: Union = @import("zon/union2.zon");
+        const union3: Union = @import("zon/union3.zon");
+
+        try expectEqual(1.5, union1.x);
+        try expectEqual(true, union2.y);
+        try expectEqual({}, union3.z);
+    }
+}
+
+test "struct" {
+    const Vec0 = struct {};
+    const Vec1 = struct { x: f32 };
+    const Vec2 = struct { x: f32, y: f32 };
+    const Escaped = struct { @"0": f32, foo: f32 };
+    try expectEqual(Vec0{}, @as(Vec0, @import("zon/vec0.zon")));
+    try expectEqual(Vec1{ .x = 1.5 }, @as(Vec1, @import("zon/vec1.zon")));
+    try expectEqual(Vec2{ .x = 1.5, .y = 2 }, @as(Vec2, @import("zon/vec2.zon")));
+    try expectEqual(Escaped{ .@"0" = 1.5, .foo = 2 }, @as(Escaped, @import("zon/escaped_struct.zon")));
+}
+
+test "struct default fields" {
+    const Vec3 = struct {
+        x: f32,
+        y: f32,
+        z: f32 = 123.4,
+    };
+    try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, @as(Vec3, @import("zon/vec2.zon")));
+    const ascribed: Vec3 = @import("zon/vec2.zon");
+    try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, ascribed);
+
+    const Vec2 = struct {
+        x: f32 = 20.0,
+        y: f32 = 10.0,
+    };
+    try expectEqual(Vec2{ .x = 1.5, .y = 2.0 }, @as(Vec2, @import("zon/vec2.zon")));
+}
+
+test "struct enum field" {
+    const Struct = struct {
+        x: enum { x, y, z },
+    };
+    try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon")));
+}
+
+test "tuple" {
+    const Tuple = struct { f32, bool, []const u8, u16 };
+    try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
+}
+
+test "comptime fields" {
+    // Test setting comptime tuple fields to the correct value
+    {
+        const Tuple = struct {
+            comptime f32 = 1.2,
+            comptime bool = true,
+            comptime []const u8 = "hello",
+            comptime u16 = 3,
+        };
+        try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
+    }
+
+    // Test setting comptime struct fields to the correct value
+    {
+        const Vec2 = struct {
+            comptime x: f32 = 1.5,
+            comptime y: f32 = 2.0,
+        };
+        try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/vec2.zon")));
+    }
+
+    // Test allowing comptime tuple fields to be set to their defaults
+    {
+        const Tuple = struct {
+            f32,
+            bool,
+            []const u8,
+            u16,
+            comptime u8 = 255,
+        };
+        try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
+    }
+
+    // Test allowing comptime struct fields to be set to their defaults
+    {
+        const Vec2 = struct {
+            comptime x: f32 = 1.5,
+            comptime y: f32 = 2.0,
+        };
+        try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/slice-empty.zon")));
+    }
+}
+
+test "char" {
+    try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon")));
+    try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon")));
+}
+
+test "arrays" {
+    try expectEqual([0]u8{}, @as([0]u8, @import("zon/vec0.zon")));
+    try expectEqual([0:1]u8{}, @as([0:1]u8, @import("zon/vec0.zon")));
+    try expectEqual(1, @as([0:1]u8, @import("zon/vec0.zon"))[0]);
+    try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @as([4]u8, @import("zon/array.zon")));
+    try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @as([4:2]u8, @import("zon/array.zon")));
+    try expectEqual(2, @as([4:2]u8, @import("zon/array.zon"))[4]);
+}
+
+test "slices, arrays, tuples" {
+    {
+        const expected_slice: []const u8 = &.{};
+        const found_slice: []const u8 = @import("zon/slice-empty.zon");
+        try expectEqualSlices(u8, expected_slice, found_slice);
+
+        const expected_array: [0]u8 = .{};
+        const found_array: [0]u8 = @import("zon/slice-empty.zon");
+        try expectEqual(expected_array, found_array);
+
+        const T = struct {};
+        const expected_tuple: T = .{};
+        const found_tuple: T = @import("zon/slice-empty.zon");
+        try expectEqual(expected_tuple, found_tuple);
+    }
+
+    {
+        const expected_slice: []const u8 = &.{1};
+        const found_slice: []const u8 = @import("zon/slice1_no_newline.zon");
+        try expectEqualSlices(u8, expected_slice, found_slice);
+
+        const expected_array: [1]u8 = .{1};
+        const found_array: [1]u8 = @import("zon/slice1_no_newline.zon");
+        try expectEqual(expected_array, found_array);
+
+        const T = struct { u8 };
+        const expected_tuple: T = .{1};
+        const found_tuple: T = @import("zon/slice1_no_newline.zon");
+        try expectEqual(expected_tuple, found_tuple);
+    }
+
+    {
+        const expected_slice: []const u8 = &.{ 'a', 'b', 'c' };
+        const found_slice: []const u8 = @import("zon/slice-abc.zon");
+        try expectEqualSlices(u8, expected_slice, found_slice);
+
+        const expected_array: [3]u8 = .{ 'a', 'b', 'c' };
+        const found_array: [3]u8 = @import("zon/slice-abc.zon");
+        try expectEqual(expected_array, found_array);
+
+        const T = struct { u8, u8, u8 };
+        const expected_tuple: T = .{ 'a', 'b', 'c' };
+        const found_tuple: T = @import("zon/slice-abc.zon");
+        try expectEqual(expected_tuple, found_tuple);
+    }
+}
+
+test "string literals" {
+    try expectEqualSlices(u8, "abc", @import("zon/abc.zon"));
+    try expectEqualSlices(u8, "ab\\c", @import("zon/abc-escaped.zon"));
+    const zero_terminated: [:0]const u8 = @import("zon/abc.zon");
+    try expectEqualDeep(zero_terminated, "abc");
+    try expectEqual(0, zero_terminated[zero_terminated.len]);
+    try expectEqualStrings(
+        \\Hello, world!
+        \\This is a multiline string!
+        \\ There are no escapes, we can, for example, include \n in the string
+    , @import("zon/multiline_string.zon"));
+    try expectEqualStrings("a\nb\x00c", @import("zon/string_embedded_null.zon"));
+}
+
+test "enum literals" {
+    const Enum = enum {
+        foo,
+        bar,
+        baz,
+        @"0\na",
+    };
+    try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon")));
+    try expectEqual(.foo, @as(@TypeOf(.foo), @import("zon/foo.zon")));
+    try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon")));
+}
+
+test "int" {
+    const T = struct {
+        u8,
+        i16,
+        i14,
+        i32,
+        i8,
+        i8,
+        u8,
+        u8,
+        u65,
+        u65,
+        i128,
+        i128,
+        i66,
+        i66,
+        i8,
+        i8,
+        i16,
+        i16,
+        i16,
+        i16,
+        i16,
+        i16,
+        u65,
+        i66,
+        i66,
+        u65,
+        i66,
+        i66,
+        u65,
+        i66,
+        i66,
+    };
+    const expected: T = .{
+        // Test various numbers and types
+        10,
+        24,
+        -4,
+        -123,
+
+        // Test limits
+        127,
+        -128,
+
+        // Test characters
+        'a',
+        'z',
+
+        // Test big integers
+        36893488147419103231,
+        36893488147419103231,
+        -18446744073709551615, // Only a big int due to negation
+        -9223372036854775809, // Only a big int due to negation
+
+        // Test big integer limits
+        36893488147419103231,
+        -36893488147419103232,
+
+        // Test parsing whole number floats as integers
+        -1,
+        123,
+
+        // Test non-decimal integers
+        0xff,
+        -0xff,
+        0o77,
+        -0o77,
+        0b11,
+        -0b11,
+
+        // Test non-decimal big integers
+        0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+        -0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+        -0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+        -0x1ffffffffffffffff,
+    };
+    const actual: T = @import("zon/ints.zon");
+    try expectEqual(expected, actual);
+}
+
+test "floats" {
+    const T = struct {
+        f16,
+        f32,
+        f64,
+        f128,
+        f16,
+        f16,
+        f32,
+        f32,
+        f32,
+        f32,
+        f32,
+        f32,
+        f128,
+        f32,
+        f32,
+        f32,
+        f32,
+        f32,
+    };
+    const expected: T = .{
+        // Test decimals
+        0.5,
+        123.456,
+        -123.456,
+        42.5,
+
+        // Test whole numbers with and without decimals
+        5.0,
+        5.0,
+        -102,
+        -102,
+
+        // Test characters and negated characters
+        'a',
+        'z',
+
+        // Test big integers
+        36893488147419103231,
+        -36893488147419103231,
+        0x1ffffffffffffffff,
+        0x1ffffffffffffffff,
+
+        // Exponents, underscores
+        123.0E+77,
+
+        // Hexadecimal
+        0x103.70p-5,
+        -0x103.70,
+        0x1234_5678.9ABC_CDEFp-10,
+    };
+    const actual: T = @import("zon/floats.zon");
+    try expectEqual(expected, actual);
+}
+
+test "inf and nan" {
+    // f32
+    {
+        const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon");
+        try expect(std.math.isNan(actual[0]));
+        try expect(std.math.isPositiveInf(actual[1]));
+        try expect(std.math.isNegativeInf(actual[2]));
+    }
+
+    // f128
+    {
+        const actual: struct { f128, f128, f128 } = @import("zon/inf_and_nan.zon");
+        try expect(std.math.isNan(actual[0]));
+        try expect(std.math.isPositiveInf(actual[1]));
+        try expect(std.math.isNegativeInf(actual[2]));
+    }
+}
+
+test "vector" {
+    {
+        const actual: @Vector(0, bool) = @import("zon/vec0.zon");
+        const expected: @Vector(0, bool) = .{};
+        try expectEqual(expected, actual);
+    }
+    {
+        const actual: @Vector(3, bool) = @import("zon/vec3_bool.zon");
+        const expected: @Vector(3, bool) = .{ false, false, true };
+        try expectEqual(expected, actual);
+    }
+
+    {
+        const actual: @Vector(0, f32) = @import("zon/vec0.zon");
+        const expected: @Vector(0, f32) = .{};
+        try expectEqual(expected, actual);
+    }
+    {
+        const actual: @Vector(3, f32) = @import("zon/vec3_float.zon");
+        const expected: @Vector(3, f32) = .{ 1.5, 2.5, 3.5 };
+        try expectEqual(expected, actual);
+    }
+
+    {
+        const actual: @Vector(0, u8) = @import("zon/vec0.zon");
+        const expected: @Vector(0, u8) = .{};
+        try expectEqual(expected, actual);
+    }
+    {
+        const actual: @Vector(3, u8) = @import("zon/vec3_int.zon");
+        const expected: @Vector(3, u8) = .{ 2, 4, 6 };
+        try expectEqual(expected, actual);
+    }
+
+    {
+        const actual: @Vector(0, *const u8) = @import("zon/vec0.zon");
+        const expected: @Vector(0, *const u8) = .{};
+        try expectEqual(expected, actual);
+    }
+    {
+        const actual: @Vector(3, *const u8) = @import("zon/vec3_int.zon");
+        const expected: @Vector(3, *const u8) = .{ &2, &4, &6 };
+        try expectEqual(expected, actual);
+    }
+
+    {
+        const actual: @Vector(0, ?*const u8) = @import("zon/vec0.zon");
+        const expected: @Vector(0, ?*const u8) = .{};
+        try expectEqual(expected, actual);
+    }
+    {
+        const actual: @Vector(3, ?*const u8) = @import("zon/vec3_int_opt.zon");
+        const expected: @Vector(3, ?*const u8) = .{ &2, null, &6 };
+        try expectEqual(expected, actual);
+    }
+}
+
+test "pointers" {
+    // Primitive with varying levels of pointers
+    try expectEqual(@as(u8, 'a'), @as(*const u8, @import("zon/a.zon")).*);
+    try expectEqual(@as(u8, 'a'), @as(*const *const u8, @import("zon/a.zon")).*.*);
+    try expectEqual(@as(u8, 'a'), @as(*const *const *const u8, @import("zon/a.zon")).*.*.*);
+
+    // Primitive optional with varying levels of pointers
+    try expectEqual(@as(u8, 'a'), @as(?*const u8, @import("zon/a.zon")).?.*);
+    try expectEqual(null, @as(?*const u8, @import("zon/none.zon")));
+
+    try expectEqual(@as(u8, 'a'), @as(*const ?u8, @import("zon/a.zon")).*.?);
+    try expectEqual(null, @as(*const ?u8, @import("zon/none.zon")).*);
+
+    try expectEqual(@as(u8, 'a'), @as(?*const *const u8, @import("zon/a.zon")).?.*.*);
+    try expectEqual(null, @as(?*const *const u8, @import("zon/none.zon")));
+
+    try expectEqual(@as(u8, 'a'), @as(*const ?*const u8, @import("zon/a.zon")).*.?.*);
+    try expectEqual(null, @as(*const ?*const u8, @import("zon/none.zon")).*);
+
+    try expectEqual(@as(u8, 'a'), @as(*const *const ?u8, @import("zon/a.zon")).*.*.?);
+    try expectEqual(null, @as(*const *const ?u8, @import("zon/none.zon")).*.*);
+
+    try expectEqual([3]u8{ 2, 4, 6 }, @as(*const [3]u8, @import("zon/vec3_int.zon")).*);
+
+    // 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: ?*const Outer = @import("zon/complex.zon");
+        try std.testing.expectEqualDeep(expected, found.?.*);
+    }
+}
+
+test "recursive" {
+    const Recursive = struct { foo: ?*const @This() };
+    const expected: Recursive = .{ .foo = &.{ .foo = null } };
+    try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon")));
+}
test/cases/compile_errors/zon/addr_slice.zon
@@ -0,0 +1,3 @@
+.{
+    .value = &.{ 1, 2, 3 },
+}
test/cases/compile_errors/zon/array.zon
@@ -0,0 +1,1 @@
+.{ 'a', 'b', 'c' }
test/cases/compile_errors/zon/char_32.zon
@@ -0,0 +1,1 @@
+' '
\ No newline at end of file
test/cases/compile_errors/zon/desktop.ini
@@ -0,0 +1,3 @@
+[LocalizedFileNames]
+invalid_zon_2.zig=@invalid_zon_2.zig,0
+invalid_zon_1.zig=@invalid_zon_1.zig,0
test/cases/compile_errors/zon/doc_comment.zon
@@ -0,0 +1,2 @@
+//! Doc comments aren't allowed in ZON
+.{}
test/cases/compile_errors/zon/double_negation_float.zon
@@ -0,0 +1,1 @@
+--1.0
\ No newline at end of file
test/cases/compile_errors/zon/double_negation_int.zon
@@ -0,0 +1,1 @@
+--1
\ No newline at end of file
test/cases/compile_errors/zon/enum_embedded_null.zon
@@ -0,0 +1,4 @@
+.{
+    .@"\x00",
+    10,
+}
test/cases/compile_errors/zon/hello.zon
@@ -0,0 +1,1 @@
+"hello"
test/cases/compile_errors/zon/inf.zon
@@ -0,0 +1,1 @@
+inf
\ No newline at end of file
test/cases/compile_errors/zon/int_32.zon
@@ -0,0 +1,1 @@
+32
\ No newline at end of file
test/cases/compile_errors/zon/int_neg_33.zon
@@ -0,0 +1,1 @@
+-33
\ No newline at end of file
test/cases/compile_errors/zon/invalid_character.zon
@@ -0,0 +1,1 @@
+'\a'
test/cases/compile_errors/zon/invalid_number.zon
@@ -0,0 +1,1 @@
+368934881474191032a32
test/cases/compile_errors/zon/invalid_string.zon
@@ -0,0 +1,1 @@
+"\"\a\""
test/cases/compile_errors/zon/large_number.zon
@@ -0,0 +1,1 @@
+36893488147419103232
test/cases/compile_errors/zon/leading_zero_in_integer.zon
@@ -0,0 +1,1 @@
+0012
\ No newline at end of file
test/cases/compile_errors/zon/nan.zon
@@ -0,0 +1,1 @@
+nan
\ No newline at end of file
test/cases/compile_errors/zon/neg_char.zon
@@ -0,0 +1,1 @@
+-'a'
test/cases/compile_errors/zon/neg_inf.zon
@@ -0,0 +1,1 @@
+-inf
\ No newline at end of file
test/cases/compile_errors/zon/neg_nan.zon
@@ -0,0 +1,1 @@
+-nan
test/cases/compile_errors/zon/negative_zero.zon
@@ -0,0 +1,1 @@
+-0
\ No newline at end of file
test/cases/compile_errors/zon/simple_union.zon
@@ -0,0 +1,1 @@
+.{ .a = 10 }
test/cases/compile_errors/zon/struct.zon
@@ -0,0 +1,4 @@
+.{
+    .boolean = true,
+    .number = 123,
+}
test/cases/compile_errors/zon/struct_dup_field.zon
@@ -0,0 +1,4 @@
+.{
+    .name = 10,
+    .name = 20,
+}
test/cases/compile_errors/zon/syntax_error.zon
@@ -0,0 +1,4 @@
+.{
+    .boolean = true
+    .number = 123,
+}
test/cases/compile_errors/zon/tuple.zon
@@ -0,0 +1,1 @@
+.{ 1.5, 2 }
test/cases/compile_errors/zon/type_decl.zon
@@ -0,0 +1,3 @@
+.{
+    .foo = struct {},
+}
test/cases/compile_errors/zon/type_expr_array.zon
@@ -0,0 +1,1 @@
+[3]i32{1, 2, 3}
\ No newline at end of file
test/cases/compile_errors/zon/type_expr_fn.zon
@@ -0,0 +1,1 @@
+fn foo() void {}
test/cases/compile_errors/zon/type_expr_struct.zon
@@ -0,0 +1,1 @@
+Vec2{ .x = 1.0, .y = 2.0 }
\ No newline at end of file
test/cases/compile_errors/zon/type_expr_tuple.zon
@@ -0,0 +1,1 @@
+Vec2{1.0,  2.0}
\ No newline at end of file
test/cases/compile_errors/zon/unescaped_newline.zon
@@ -0,0 +1,2 @@
+"a
+b"
\ No newline at end of file
test/cases/compile_errors/zon/unknown_ident.zon
@@ -0,0 +1,3 @@
+.{
+    .value = truefalse,
+}
test/cases/compile_errors/zon/vec2.zon
@@ -0,0 +1,1 @@
+.{ .x = 1.5, .y = 2 }
test/cases/compile_errors/zon/void.zon
@@ -0,0 +1,1 @@
+.{ .foo = {} }
test/cases/compile_errors/@import_zon_addr_slice.zig
@@ -0,0 +1,9 @@
+pub fn main() void {
+    const f: struct { value: []const i32 } = @import("zon/addr_slice.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/addr_slice.zon
+//
+// addr_slice.zon:2:14: error: pointers are not available in ZON
test/cases/compile_errors/@import_zon_array_len.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: [4]u8 = @import("zon/array.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/array.zon
+//
+// array.zon:1:2: error: expected type '[4]u8'
+// tmp.zig:2:30: note: imported here
test/cases/compile_errors/@import_zon_bad_import.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    _ = @import(
+        "bogus-does-not-exist.zon",
+    );
+}
+
+// error
+//
+// :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound
test/cases/compile_errors/@import_zon_bad_type.zig
@@ -0,0 +1,125 @@
+export fn testVoid() void {
+    const f: void = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testInStruct() void {
+    const f: struct { f: [*]const u8 } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testError() void {
+    const f: struct { error{foo} } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testInUnion() void {
+    const f: union(enum) { a: void, b: [*c]const u8 } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testInVector() void {
+    const f: @Vector(0, [*c]const u8) = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testInOpt() void {
+    const f: *const ?[*c]const u8 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testComptimeField() void {
+    const f: struct { comptime foo: ??u8 = null } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testEnumLiteral() void {
+    const f: @TypeOf(.foo) = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testNestedOpt1() void {
+    const f: ??u8 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testNestedOpt2() void {
+    const f: ?*const ?u8 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testNestedOpt3() void {
+    const f: *const ?*const ?*const u8 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testOpt() void {
+    const f: ?u8 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testNonExhaustiveEnum() void {
+    const f: enum(u8) { _ } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testUntaggedUnion() void {
+    const f: union { foo: void } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testTaggedUnionVoid() void {
+    const f: union(enum) { foo: void } = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testVisited() void {
+    const V = struct {
+        ?f32, // Adds `?f32` to the visited list
+        ??f32, // `?f32` is already visited, we need to detect the nested opt anyway
+        f32,
+    };
+    const f: V = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+export fn testMutablePointer() void {
+    const f: *i32 = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/neg_inf.zon
+//
+// tmp.zig:2:29: error: type 'void' is not available in ZON
+// tmp.zig:7:50: error: type '[*]const u8' is not available in ZON
+// tmp.zig:7:50: note: ZON does not allow many-pointers
+// tmp.zig:12:46: error: type 'error{foo}' is not available in ZON
+// tmp.zig:17:65: error: type '[*c]const u8' is not available in ZON
+// tmp.zig:17:65: note: ZON does not allow C pointers
+// tmp.zig:22:49: error: type '[*c]const u8' is not available in ZON
+// tmp.zig:22:49: note: ZON does not allow C pointers
+// tmp.zig:27:45: error: type '[*c]const u8' is not available in ZON
+// tmp.zig:27:45: note: ZON does not allow C pointers
+// tmp.zig:32:61: error: type '??u8' is not available in ZON
+// tmp.zig:32:61: note: ZON does not allow nested optionals
+// tmp.zig:42:29: error: type '??u8' is not available in ZON
+// tmp.zig:42:29: note: ZON does not allow nested optionals
+// tmp.zig:47:36: error: type '?*const ?u8' is not available in ZON
+// tmp.zig:47:36: note: ZON does not allow nested optionals
+// tmp.zig:52:50: error: type '?*const ?*const u8' is not available in ZON
+// tmp.zig:52:50: note: ZON does not allow nested optionals
+// tmp.zig:82:26: error: type '??f32' is not available in ZON
+// tmp.zig:82:26: note: ZON does not allow nested optionals
+// tmp.zig:87:29: error: type '*i32' is not available in ZON
+// tmp.zig:87:29: note: ZON does not allow mutable pointers
+// neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)'
+// tmp.zig:37:38: note: imported here
+// neg_inf.zon:1:1: error: expected type '?u8'
+// tmp.zig:57:28: note: imported here
+// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_490'
+// tmp.zig:62:39: note: imported here
+// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_492'
+// tmp.zig:67:44: note: imported here
+// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_495'
+// tmp.zig:72:50: note: imported here
test/cases/compile_errors/@import_zon_comptime_inf.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: comptime_float = @import("zon/inf.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/inf.zon
+//
+// inf.zon:1:1: error: expected type 'comptime_float'
+// tmp.zig:2:39: note: imported here
test/cases/compile_errors/@import_zon_comptime_nan.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: comptime_float = @import("zon/nan.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/nan.zon
+//
+// nan.zon:1:1: error: expected type 'comptime_float'
+// tmp.zig:2:39: note: imported here
test/cases/compile_errors/@import_zon_comptime_neg_inf.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: comptime_float = @import("zon/neg_inf.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/neg_inf.zon
+//
+// neg_inf.zon:1:1: error: expected type 'comptime_float'
+// tmp.zig:2:39: note: imported here
test/cases/compile_errors/@import_zon_doc_comment.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: struct { foo: type } = @import("zon/doc_comment.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/doc_comment.zon
+//
+// doc_comment.zon:1:1: error: expected expression, found 'a document comment'
test/cases/compile_errors/@import_zon_double_negation_float.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: f32 = @import("zon/double_negation_float.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/double_negation_float.zon
+//
+// double_negation_float.zon:1:1: error: expected number or 'inf' after '-'
test/cases/compile_errors/@import_zon_double_negation_int.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: i32 = @import("zon/double_negation_int.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/double_negation_int.zon
+//
+// double_negation_int.zon:1:1: error: expected number or 'inf' after '-'
test/cases/compile_errors/@import_zon_enum_embedded_null.zig
@@ -0,0 +1,11 @@
+const std = @import("std");
+export fn entry() void {
+    const E = enum { foo };
+    const f: struct { E, E } = @import("zon/enum_embedded_null.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/enum_embedded_null.zon
+//
+// enum_embedded_null.zon:2:6: error: identifier cannot contain null bytes
test/cases/compile_errors/@import_zon_expected_void.zig
@@ -0,0 +1,11 @@
+export fn entry() void {
+    const U = union(enum) { a: void };
+    const f: U = @import("zon/simple_union.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/simple_union.zon
+//
+// simple_union.zon:1:9: error: expected type 'void'
+// tmp.zig:3:26: note: imported here
test/cases/compile_errors/@import_zon_invalid_character.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: u8 = @import("zon/invalid_character.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/invalid_character.zon
+//
+// invalid_character.zon:1:3: error: invalid escape character: 'a'
test/cases/compile_errors/@import_zon_invalid_number.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: u128 = @import("zon/invalid_number.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/invalid_number.zon
+//
+// invalid_number.zon:1:19: error: invalid digit 'a' for decimal base
test/cases/compile_errors/@import_zon_invalid_string.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: []const u8 = @import("zon/invalid_string.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/invalid_string.zon
+//
+// invalid_string.zon:1:5: error: invalid escape character: 'a'
test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: u128 = @import("zon/leading_zero_in_integer.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/leading_zero_in_integer.zon
+//
+// leading_zero_in_integer.zon:1:1: error: number '0012' has leading zero
+// leading_zero_in_integer.zon:1:1: note: use '0o' prefix for octal literals
test/cases/compile_errors/@import_zon_neg_char.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: u8 = @import("zon/neg_char.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/neg_char.zon
+//
+// neg_char.zon:1:1: error: expected number or 'inf' after '-'
test/cases/compile_errors/@import_zon_neg_nan.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: u8 = @import("zon/neg_nan.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/neg_nan.zon
+//
+// neg_nan.zon:1:1: error: expected number or 'inf' after '-'
test/cases/compile_errors/@import_zon_negative_zero.zig
@@ -0,0 +1,11 @@
+export fn entry() void {
+    const f: i8 = @import("zon/negative_zero.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/negative_zero.zon
+//
+// negative_zero.zon:1:2: error: integer literal '-0' is ambiguous
+// negative_zero.zon:1:2: note: use '0' for an integer zero
+// negative_zero.zon:1:2: note: use '-0.0' for a floating-point signed zero
test/cases/compile_errors/@import_zon_no_rt.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f = @import("zon/simple_union.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/simple_union.zon
+//
+// tmp.zig:2:23: error: '@import' of ZON must have a known result type
test/cases/compile_errors/@import_zon_number_fail_limits.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: i66 = @import("zon/large_number.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/large_number.zon
+//
+// large_number.zon:1:1: error: type 'i66' cannot represent integer value '36893488147419103232'
+// tmp.zig:2:28: note: imported here
test/cases/compile_errors/@import_zon_oob_char_0.zig
@@ -0,0 +1,16 @@
+export fn entry() void {
+    {
+        const f: u6 = @import("zon/char_32.zon");
+        _ = f;
+    }
+    {
+        const f: u5 = @import("zon/char_32.zon");
+        _ = f;
+    }
+}
+
+// error
+// imports=zon/char_32.zon
+//
+// char_32.zon:1:1: error: type 'u5' cannot represent integer value '32'
+// tmp.zig:7:31: note: imported here
test/cases/compile_errors/@import_zon_oob_char_1.zig
@@ -0,0 +1,16 @@
+export fn entry() void {
+    {
+        const f: i7 = @import("zon/char_32.zon");
+        _ = f;
+    }
+    {
+        const f: i6 = @import("zon/char_32.zon");
+        _ = f;
+    }
+}
+
+// error
+// imports=zon/char_32.zon
+//
+// char_32.zon:1:1: error: type 'i6' cannot represent integer value '32'
+// tmp.zig:7:31: note: imported here
test/cases/compile_errors/@import_zon_oob_int_0.zig
@@ -0,0 +1,16 @@
+export fn entry() void {
+    {
+        const f: u6 = @import("zon/int_32.zon");
+        _ = f;
+    }
+    {
+        const f: u5 = @import("zon/int_32.zon");
+        _ = f;
+    }
+}
+
+// error
+// imports=zon/int_32.zon
+//
+// int_32.zon:1:1: error: type 'u5' cannot represent integer value '32'
+// tmp.zig:7:31: note: imported here
test/cases/compile_errors/@import_zon_oob_int_1.zig
@@ -0,0 +1,16 @@
+export fn entry() void {
+    {
+        const f: i7 = @import("zon/int_32.zon");
+        _ = f;
+    }
+    {
+        const f: i6 = @import("zon/int_32.zon");
+        _ = f;
+    }
+}
+
+// error
+// imports=zon/int_32.zon
+//
+// int_32.zon:1:1: error: type 'i6' cannot represent integer value '32'
+// tmp.zig:7:31: note: imported here
test/cases/compile_errors/@import_zon_oob_int_2.zig
@@ -0,0 +1,16 @@
+export fn entry() void {
+    {
+        const f: i7 = @import("zon/int_neg_33.zon");
+        _ = f;
+    }
+    {
+        const f: i6 = @import("zon/int_neg_33.zon");
+        _ = f;
+    }
+}
+
+// error
+// imports=zon/int_neg_33.zon
+//
+// int_neg_33.zon:1:1: error: type 'i6' cannot represent integer value '-33'
+// tmp.zig:7:31: note: imported here
test/cases/compile_errors/@import_zon_oob_int_3.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: u64 = @import("zon/int_neg_33.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/int_neg_33.zon
+//
+// int_neg_33.zon:1:1: error: type 'u64' cannot represent integer value '-33'
+// tmp.zig:2:28: note: imported here
test/cases/compile_errors/@import_zon_opt_in_err.zig
@@ -0,0 +1,82 @@
+export fn testFloatA() void {
+    const f: ?f32 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testFloatB() void {
+    const f: *const ?f32 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testFloatC() void {
+    const f: ?*const f32 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testBool() void {
+    const f: ?bool = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testInt() void {
+    const f: ?i32 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+const Enum = enum { foo };
+export fn testEnum() void {
+    const f: ?Enum = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testEnumLit() void {
+    const f: ?@TypeOf(.foo) = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testArray() void {
+    const f: ?[1]u8 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+const Union = union {};
+export fn testUnion() void {
+    const f: ?Union = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testSlice() void {
+    const f: ?[]const u8 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+export fn testVector() void {
+    const f: ?@Vector(3, f32) = @import("zon/vec2.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/vec2.zon
+//
+// vec2.zon:1:2: error: expected type '?f32'
+// tmp.zig:2:29: note: imported here
+// vec2.zon:1:2: error: expected type '*const ?f32'
+// tmp.zig:7:36: note: imported here
+// vec2.zon:1:2: error: expected type '?*const f32'
+// tmp.zig:12:36: note: imported here
+// vec2.zon:1:2: error: expected type '?bool'
+// tmp.zig:17:30: note: imported here
+// vec2.zon:1:2: error: expected type '?i32'
+// tmp.zig:22:29: note: imported here
+// vec2.zon:1:2: error: expected type '?tmp.Enum'
+// tmp.zig:28:30: note: imported here
+// vec2.zon:1:2: error: expected type '?@Type(.enum_literal)'
+// tmp.zig:33:39: note: imported here
+// vec2.zon:1:2: error: expected type '?[1]u8'
+// tmp.zig:38:31: note: imported here
+// vec2.zon:1:2: error: expected type '?tmp.Union'
+// tmp.zig:44:31: note: imported here
+// vec2.zon:1:2: error: expected type '?[]const u8'
+// tmp.zig:49:36: note: imported here
+// vec2.zon:1:2: error: expected type '?@Vector(3, f32)'
+// tmp.zig:54:41: note: imported here
test/cases/compile_errors/@import_zon_opt_in_err_struct.zig
@@ -0,0 +1,19 @@
+const Struct = struct { f: bool };
+export fn testStruct() void {
+    const f: ?Struct = @import("zon/nan.zon");
+    _ = f;
+}
+
+const Tuple = struct { bool };
+export fn testTuple() void {
+    const f: ?Tuple = @import("zon/nan.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/nan.zon
+//
+//nan.zon:1:1: error: expected type '?tmp.Struct'
+//tmp.zig:3:32: note: imported here
+//nan.zon:1:1: error: expected type '?struct { bool }'
+//tmp.zig:9:31: note: imported here
test/cases/compile_errors/@import_zon_string_as_array.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: [5]u8 = @import("zon/hello.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/hello.zon
+//
+// hello.zon:1:1: error: expected type '[5]u8'
+// tmp.zig:2:30: note: imported here
test/cases/compile_errors/@import_zon_struct_dup_field.zig
@@ -0,0 +1,11 @@
+const std = @import("std");
+export fn entry() void {
+    const f: struct { name: u8 } = @import("zon/struct_dup_field.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/struct_dup_field.zon
+//
+// struct_dup_field.zon:2:6: error: duplicate struct field name
+// struct_dup_field.zon:3:6: note: duplicate name here
test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig
@@ -0,0 +1,14 @@
+export fn entry() void {
+    const Vec2 = struct {
+        comptime x: f32 = 1.5,
+        comptime y: f32 = 2.5,
+    };
+    const f: Vec2 = @import("zon/vec2.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/vec2.zon
+//
+// vec2.zon:1:19: error: value stored in comptime field does not match the default value of the field
+// tmp.zig:6:29: note: imported here
test/cases/compile_errors/@import_zon_syntax_error.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: bool = @import("zon/syntax_error.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/syntax_error.zon
+//
+// syntax_error.zon:3:13: error: expected ',' after initializer
test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig
@@ -0,0 +1,14 @@
+export fn entry() void {
+    const T = struct {
+        comptime f32 = 1.5,
+        comptime f32 = 2.5,
+    };
+    const f: T = @import("zon/tuple.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/tuple.zon
+//
+// tuple.zon:1:9: error: value stored in comptime field does not match the default value of the field
+// tmp.zig:6:26: note: imported here
test/cases/compile_errors/@import_zon_type_decl.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: struct { foo: type } = @import("zon/type_decl.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/type_decl.zon
+//
+// type_decl.zon:2:12: error: types are not available in ZON
test/cases/compile_errors/@import_zon_type_expr_array.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: [3]i32 = @import("zon/type_expr_array.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/type_expr_array.zon
+//
+// type_expr_array.zon:1:1: error: types are not available in ZON
+// type_expr_array.zon:1:1: note: replace the type with '.'
test/cases/compile_errors/@import_zon_type_expr_fn.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: i32 = @import("zon/type_expr_fn.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/type_expr_fn.zon
+//
+// type_expr_fn.zon:1:1: error: types are not available in ZON
+// type_expr_fn.zon:1:1: note: replace the type with '.'
test/cases/compile_errors/@import_zon_type_expr_struct.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: struct { x: f32, y: f32 } = @import("zon/type_expr_struct.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/type_expr_struct.zon
+//
+// type_expr_struct.zon:1:1: error: types are not available in ZON
+// type_expr_struct.zon:1:1: note: replace the type with '.'
test/cases/compile_errors/@import_zon_type_expr_tuple.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: struct { f32, f32 } = @import("zon/type_expr_tuple.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/type_expr_tuple.zon
+//
+// type_expr_tuple.zon:1:1: error: types are not available in ZON
+// type_expr_tuple.zon:1:1: note: replace the type with '.'
test/cases/compile_errors/@import_zon_type_mismatch.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: bool = @import("zon/struct.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/struct.zon
+//
+// struct.zon:1:2: error: expected type 'bool'
+// tmp.zig:2:29: note: imported here
test/cases/compile_errors/@import_zon_unescaped_newline.zig
@@ -0,0 +1,9 @@
+export fn entry() void {
+    const f: i8 = @import("zon/unescaped_newline.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/unescaped_newline.zon
+//
+// unescaped_newline.zon:1:1: error: expected expression, found 'invalid token'
test/cases/compile_errors/@import_zon_unknown_ident.zig
@@ -0,0 +1,11 @@
+export fn entry() void {
+    const f: struct { value: bool } = @import("zon/unknown_ident.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/unknown_ident.zon
+//
+// unknown_ident.zon:2:14: error: invalid expression
+// unknown_ident.zon:2:14: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'
+// unknown_ident.zon:2:14: note: precede identifier with '.' for an enum literal
test/cases/compile_errors/@import_zon_vec_too_few.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: @Vector(3, f32) = @import("zon/tuple.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/tuple.zon
+//
+// tuple.zon:1:2: error: expected 3 vector elements; found 2
+// tmp.zig:2:40: note: imported here
test/cases/compile_errors/@import_zon_vec_too_many.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: @Vector(1, f32) = @import("zon/tuple.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/tuple.zon
+//
+// tuple.zon:1:2: error: expected 1 vector elements; found 2
+// tmp.zig:2:40: note: imported here
test/cases/compile_errors/@import_zon_vec_wrong_type.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: @Vector(2, bool) = @import("zon/tuple.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/tuple.zon
+//
+// tuple.zon:1:4: error: expected type 'bool'
+// tmp.zig:2:41: note: imported here
test/cases/compile_errors/@import_zon_void.zig
@@ -0,0 +1,10 @@
+export fn entry() void {
+    const f: union { foo: void } = @import("zon/void.zon");
+    _ = f;
+}
+
+// error
+// imports=zon/void.zon
+//
+// void.zon:1:11: error: void literals are not available in ZON
+// void.zon:1:11: note: void union payloads can be represented by enum literals
test/cases/compile_errors/redundant_try.zig
@@ -44,9 +44,9 @@ comptime {
 //
 // :5:23: error: expected error union type, found 'comptime_int'
 // :10:23: error: expected error union type, found '@TypeOf(.{})'
-// :15:23: error: expected error union type, found 'tmp.test2__struct_493'
+// :15:23: error: expected error union type, found 'tmp.test2__struct_494'
 // :15:23: note: struct declared here
-// :20:27: error: expected error union type, found 'tmp.test3__struct_495'
+// :20:27: error: expected error union type, found 'tmp.test3__struct_496'
 // :20:27: note: struct declared here
 // :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }'
 // :31:13: error: expected error union type, found 'u32'
test/src/Cases.zig
@@ -90,6 +90,11 @@ pub const Case = struct {
     link_libc: bool = false,
     pic: ?bool = null,
     pie: ?bool = null,
+    /// A list of imports to cache alongside the source file.
+    imports: []const []const u8 = &.{},
+    /// Where to look for imports relative to the `cases_dir_path` given to
+    /// `lower_to_build_steps`. If null, file imports will assert.
+    import_path: ?[]const u8 = null,
 
     deps: std.ArrayList(DepModule),
 
@@ -413,6 +418,7 @@ fn addFromDirInner(
         const pic = try manifest.getConfigForKeyAssertSingle("pic", ?bool);
         const pie = try manifest.getConfigForKeyAssertSingle("pie", ?bool);
         const emit_bin = try manifest.getConfigForKeyAssertSingle("emit_bin", bool);
+        const imports = try manifest.getConfigForKeyAlloc(ctx.arena, "imports", []const u8);
 
         if (manifest.type == .translate_c) {
             for (c_frontends) |c_frontend| {
@@ -470,7 +476,7 @@ fn addFromDirInner(
                 const next = ctx.cases.items.len;
                 try ctx.cases.append(.{
                     .name = std.fs.path.stem(filename),
-                    .target = resolved_target,
+                    .import_path = std.fs.path.dirname(filename),
                     .backend = backend,
                     .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
                     .emit_bin = emit_bin,
@@ -480,6 +486,8 @@ fn addFromDirInner(
                     .pic = pic,
                     .pie = pie,
                     .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
+                    .imports = imports,
+                    .target = resolved_target,
                 });
                 try cases.append(next);
             }
@@ -619,6 +627,7 @@ pub fn lowerToBuildSteps(
 ) void {
     const host = std.zig.system.resolveTargetQuery(.{}) catch |err|
         std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
+    const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
 
     for (self.incremental_cases.items) |incr_case| {
         if (true) {
@@ -662,11 +671,21 @@ pub fn lowerToBuildSteps(
             file_sources.put(file.path, writefiles.add(file.path, file.src)) catch @panic("OOM");
         }
 
+        for (case.imports) |import_rel| {
+            const import_abs = std.fs.path.join(b.allocator, &.{
+                cases_dir_path,
+                case.import_path orelse @panic("import_path not set"),
+                import_rel,
+            }) catch @panic("OOM");
+            _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel);
+        }
+
         const mod = b.createModule(.{
             .root_source_file = root_source_file,
             .target = case.target,
             .optimize = case.optimize_mode,
         });
+
         if (case.link_libc) mod.link_libc = true;
         if (case.pic) |pic| mod.pic = pic;
         for (case.deps.items) |dep| {
@@ -962,6 +981,8 @@ const TestManifestConfigDefaults = struct {
             return "null";
         } else if (std.mem.eql(u8, key, "pie")) {
             return "null";
+        } else if (std.mem.eql(u8, key, "imports")) {
+            return "";
         } else unreachable;
     }
 };
@@ -998,6 +1019,7 @@ const TestManifest = struct {
         .{ "backend", {} },
         .{ "pic", {} },
         .{ "pie", {} },
+        .{ "imports", {} },
     });
 
     const Type = enum {
@@ -1020,7 +1042,7 @@ const TestManifest = struct {
 
     fn ConfigValueIterator(comptime T: type) type {
         return struct {
-            inner: std.mem.SplitIterator(u8, .scalar),
+            inner: std.mem.TokenIterator(u8, .scalar),
 
             fn next(self: *@This()) !?T {
                 const next_raw = self.inner.next() orelse return null;
@@ -1098,7 +1120,9 @@ const TestManifest = struct {
             // Parse key=value(s)
             var kv_it = std.mem.splitScalar(u8, trimmed, '=');
             const key = kv_it.first();
-            if (!valid_keys.has(key)) return error.InvalidKey;
+            if (!valid_keys.has(key)) {
+                return error.InvalidKey;
+            }
             try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig);
         }
 
@@ -1115,7 +1139,7 @@ const TestManifest = struct {
     ) ConfigValueIterator(T) {
         const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key);
         return ConfigValueIterator(T){
-            .inner = std.mem.splitScalar(u8, bytes, ','),
+            .inner = std.mem.tokenizeScalar(u8, bytes, ','),
         };
     }
 
@@ -1232,6 +1256,18 @@ const TestManifest = struct {
                     return try getDefaultParser(o.child)(str);
                 }
             }.parse,
+            .@"struct" => @compileError("no default parser for " ++ @typeName(T)),
+            .pointer => {
+                if (T == []const u8) {
+                    return struct {
+                        fn parse(str: []const u8) anyerror!T {
+                            return str;
+                        }
+                    }.parse;
+                } else {
+                    @compileError("no default parser for " ++ @typeName(T));
+                }
+            },
             else => @compileError("no default parser for " ++ @typeName(T)),
         }
     }
build.zig
@@ -406,7 +406,7 @@ pub fn build(b: *std.Build) !void {
     const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
 
     const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" };
-    const fmt_exclude_paths = &.{"test/cases"};
+    const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" };
     const do_fmt = b.addFmt(.{
         .paths = fmt_include_paths,
         .exclude_paths = fmt_exclude_paths,