diff --git a/build.zig b/build.zig index a524af2923c4..dc3d4cce184a 100644 --- a/build.zig +++ b/build.zig @@ -407,7 +407,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, diff --git a/lib/compiler/aro/aro/Value.zig b/lib/compiler/aro/aro/Value.zig index 892a09b1d67d..f736d63adf7d 100644 --- a/lib/compiler/aro/aro/Value.zig +++ b/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 { diff --git a/lib/compiler/aro/backend/Interner.zig b/lib/compiler/aro/backend/Interner.zig index 818afe869116..0a910cc93c04 100644 --- a/lib/compiler/aro/backend/Interner.zig +++ b/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; } diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 3e9109dd1ac5..0af1a052b60f 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2175,10 +2175,13 @@ pub const Const = struct { TargetTooSmall, }; - /// Convert self to type T. + /// Deprecated; use `toInt`. + pub const to = toInt; + + /// Convert self to integer type T. /// /// Returns an error if self cannot be narrowed into the requested type without truncation. - pub fn to(self: Const, comptime T: type) ConvertError!T { + pub fn toInt(self: Const, comptime T: type) ConvertError!T { switch (@typeInfo(T)) { .int => |info| { // Make sure -0 is handled correctly. @@ -2216,7 +2219,26 @@ pub const Const = struct { } } }, - else => @compileError("cannot convert Const to type " ++ @typeName(T)), + else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"), + } + } + + /// Convert self to float type T. + pub fn toFloat(self: Const, comptime T: type) T { + if (self.limbs.len == 0) return 0; + + const base = std.math.maxInt(std.math.big.Limb) + 1; + var result: f128 = 0; + var i: usize = self.limbs.len; + while (i != 0) { + i -= 1; + const limb: f128 = @floatFromInt(self.limbs[i]); + result = @mulAdd(f128, base, result, limb); + } + if (self.positive) { + return @floatCast(result); + } else { + return @floatCast(-result); } } @@ -2775,11 +2797,19 @@ pub const Managed = struct { pub const ConvertError = Const.ConvertError; - /// Convert self to type T. + /// Deprecated; use `toInt`. + pub const to = toInt; + + /// Convert self to integer type T. /// /// Returns an error if self cannot be narrowed into the requested type without truncation. - pub fn to(self: Managed, comptime T: type) ConvertError!T { - return self.toConst().to(T); + pub fn toInt(self: Managed, comptime T: type) ConvertError!T { + return self.toConst().toInt(T); + } + + /// Convert self to float type T. + pub fn toFloat(self: Managed, comptime T: type) T { + return self.toConst().toFloat(T); } /// Set self from the string representation `value`. diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 2e0ccc96c17b..a8907fdae052 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -53,21 +53,21 @@ test "comptime_int to" { var a = try Managed.initSet(testing.allocator, 0xefffffff00000001eeeeeeefaaaaaaab); defer a.deinit(); - try testing.expect((try a.to(u128)) == 0xefffffff00000001eeeeeeefaaaaaaab); + try testing.expect((try a.toInt(u128)) == 0xefffffff00000001eeeeeeefaaaaaaab); } test "sub-limb to" { var a = try Managed.initSet(testing.allocator, 10); defer a.deinit(); - try testing.expect((try a.to(u8)) == 10); + try testing.expect((try a.toInt(u8)) == 10); } test "set negative minimum" { var a = try Managed.initSet(testing.allocator, @as(i64, minInt(i64))); defer a.deinit(); - try testing.expect((try a.to(i64)) == minInt(i64)); + try testing.expect((try a.toInt(i64)) == minInt(i64)); } test "set double-width maximum then zero" { @@ -75,14 +75,14 @@ test "set double-width maximum then zero" { defer a.deinit(); try a.set(@as(DoubleLimb, 0)); - try testing.expectEqual(@as(DoubleLimb, 0), try a.to(DoubleLimb)); + try testing.expectEqual(@as(DoubleLimb, 0), try a.toInt(DoubleLimb)); } test "to target too small error" { var a = try Managed.initSet(testing.allocator, 0xffffffff); defer a.deinit(); - try testing.expectError(error.TargetTooSmall, a.to(u8)); + try testing.expectError(error.TargetTooSmall, a.toInt(u8)); } test "normalize" { @@ -191,28 +191,28 @@ test "bitcount/to" { try a.set(0); try testing.expect(a.bitCountTwosComp() == 0); - try testing.expect((try a.to(u0)) == 0); - try testing.expect((try a.to(i0)) == 0); + try testing.expect((try a.toInt(u0)) == 0); + try testing.expect((try a.toInt(i0)) == 0); try a.set(-1); try testing.expect(a.bitCountTwosComp() == 1); - try testing.expect((try a.to(i1)) == -1); + try testing.expect((try a.toInt(i1)) == -1); try a.set(-8); try testing.expect(a.bitCountTwosComp() == 4); - try testing.expect((try a.to(i4)) == -8); + try testing.expect((try a.toInt(i4)) == -8); try a.set(127); try testing.expect(a.bitCountTwosComp() == 7); - try testing.expect((try a.to(u7)) == 127); + try testing.expect((try a.toInt(u7)) == 127); try a.set(-128); try testing.expect(a.bitCountTwosComp() == 8); - try testing.expect((try a.to(i8)) == -128); + try testing.expect((try a.toInt(i8)) == -128); try a.set(-129); try testing.expect(a.bitCountTwosComp() == 9); - try testing.expect((try a.to(i9)) == -129); + try testing.expect((try a.toInt(i9)) == -129); } test "fits" { @@ -248,7 +248,7 @@ test "string set" { defer a.deinit(); try a.setString(10, "120317241209124781241290847124"); - try testing.expect((try a.to(u128)) == 120317241209124781241290847124); + try testing.expect((try a.toInt(u128)) == 120317241209124781241290847124); } test "string negative" { @@ -256,7 +256,7 @@ test "string negative" { defer a.deinit(); try a.setString(10, "-1023"); - try testing.expect((try a.to(i32)) == -1023); + try testing.expect((try a.toInt(i32)) == -1023); } test "string set number with underscores" { @@ -264,7 +264,7 @@ test "string set number with underscores" { defer a.deinit(); try a.setString(10, "__1_2_0_3_1_7_2_4_1_2_0_____9_1__2__4_7_8_1_2_4_1_2_9_0_8_4_7_1_2_4___"); - try testing.expect((try a.to(u128)) == 120317241209124781241290847124); + try testing.expect((try a.toInt(u128)) == 120317241209124781241290847124); } test "string set case insensitive number" { @@ -272,7 +272,7 @@ test "string set case insensitive number" { defer a.deinit(); try a.setString(16, "aB_cD_eF"); - try testing.expect((try a.to(u32)) == 0xabcdef); + try testing.expect((try a.toInt(u32)) == 0xabcdef); } test "string set bad char error" { @@ -306,11 +306,11 @@ fn testTwosComplementLimit(comptime T: type) !void { try a.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits); const max: T = maxInt(T); - try testing.expect(max == try a.to(T)); + try testing.expect(max == try a.toInt(T)); try a.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits); const min: T = minInt(T); - try testing.expect(min == try a.to(T)); + try testing.expect(min == try a.toInt(T)); } test "string to" { @@ -381,12 +381,12 @@ test "clone" { var b = try a.clone(); defer b.deinit(); - try testing.expect((try a.to(u32)) == 1234); - try testing.expect((try b.to(u32)) == 1234); + try testing.expect((try a.toInt(u32)) == 1234); + try testing.expect((try b.toInt(u32)) == 1234); try a.set(77); - try testing.expect((try a.to(u32)) == 77); - try testing.expect((try b.to(u32)) == 1234); + try testing.expect((try a.toInt(u32)) == 77); + try testing.expect((try b.toInt(u32)) == 1234); } test "swap" { @@ -395,20 +395,20 @@ test "swap" { var b = try Managed.initSet(testing.allocator, 5678); defer b.deinit(); - try testing.expect((try a.to(u32)) == 1234); - try testing.expect((try b.to(u32)) == 5678); + try testing.expect((try a.toInt(u32)) == 1234); + try testing.expect((try b.toInt(u32)) == 5678); a.swap(&b); - try testing.expect((try a.to(u32)) == 5678); - try testing.expect((try b.to(u32)) == 1234); + try testing.expect((try a.toInt(u32)) == 5678); + try testing.expect((try b.toInt(u32)) == 1234); } test "to negative" { var a = try Managed.initSet(testing.allocator, -10); defer a.deinit(); - try testing.expect((try a.to(i32)) == -10); + try testing.expect((try a.toInt(i32)) == -10); } test "compare" { @@ -466,10 +466,10 @@ test "abs" { defer a.deinit(); a.abs(); - try testing.expect((try a.to(u32)) == 5); + try testing.expect((try a.toInt(u32)) == 5); a.abs(); - try testing.expect((try a.to(u32)) == 5); + try testing.expect((try a.toInt(u32)) == 5); } test "negate" { @@ -477,10 +477,10 @@ test "negate" { defer a.deinit(); a.negate(); - try testing.expect((try a.to(i32)) == -5); + try testing.expect((try a.toInt(i32)) == -5); a.negate(); - try testing.expect((try a.to(i32)) == 5); + try testing.expect((try a.toInt(i32)) == 5); } test "add single-single" { @@ -493,7 +493,7 @@ test "add single-single" { defer c.deinit(); try c.add(&a, &b); - try testing.expect((try c.to(u32)) == 55); + try testing.expect((try c.toInt(u32)) == 55); } test "add multi-single" { @@ -506,10 +506,10 @@ test "add multi-single" { defer c.deinit(); try c.add(&a, &b); - try testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2); + try testing.expect((try c.toInt(DoubleLimb)) == maxInt(Limb) + 2); try c.add(&b, &a); - try testing.expect((try c.to(DoubleLimb)) == maxInt(Limb) + 2); + try testing.expect((try c.toInt(DoubleLimb)) == maxInt(Limb) + 2); } test "add multi-multi" { @@ -527,7 +527,7 @@ test "add multi-multi" { defer c.deinit(); try c.add(&a, &b); - try testing.expect((try c.to(u128)) == op1 + op2); + try testing.expect((try c.toInt(u128)) == op1 + op2); } test "add zero-zero" { @@ -540,7 +540,7 @@ test "add zero-zero" { defer c.deinit(); try c.add(&a, &b); - try testing.expect((try c.to(u32)) == 0); + try testing.expect((try c.toInt(u32)) == 0); } test "add alias multi-limb nonzero-zero" { @@ -552,7 +552,7 @@ test "add alias multi-limb nonzero-zero" { try a.add(&a, &b); - try testing.expect((try a.to(u128)) == op1); + try testing.expect((try a.toInt(u128)) == op1); } test "add sign" { @@ -569,16 +569,16 @@ test "add sign" { defer neg_two.deinit(); try a.add(&one, &two); - try testing.expect((try a.to(i32)) == 3); + try testing.expect((try a.toInt(i32)) == 3); try a.add(&neg_one, &two); - try testing.expect((try a.to(i32)) == 1); + try testing.expect((try a.toInt(i32)) == 1); try a.add(&one, &neg_two); - try testing.expect((try a.to(i32)) == -1); + try testing.expect((try a.toInt(i32)) == -1); try a.add(&neg_one, &neg_two); - try testing.expect((try a.to(i32)) == -3); + try testing.expect((try a.toInt(i32)) == -3); } test "add comptime scalar" { @@ -589,7 +589,7 @@ test "add comptime scalar" { defer b.deinit(); try b.addScalar(&a, 5); - try testing.expect((try b.to(u32)) == 55); + try testing.expect((try b.toInt(u32)) == 55); } test "add scalar" { @@ -600,7 +600,7 @@ test "add scalar" { defer b.deinit(); try b.addScalar(&a, @as(u32, 31)); - try testing.expect((try b.to(u32)) == 154); + try testing.expect((try b.toInt(u32)) == 154); } test "addWrap single-single, unsigned" { @@ -613,7 +613,7 @@ test "addWrap single-single, unsigned" { const wrapped = try a.addWrap(&a, &b, .unsigned, 17); try testing.expect(wrapped); - try testing.expect((try a.to(u17)) == 9); + try testing.expect((try a.toInt(u17)) == 9); } test "subWrap single-single, unsigned" { @@ -626,7 +626,7 @@ test "subWrap single-single, unsigned" { const wrapped = try a.subWrap(&a, &b, .unsigned, 17); try testing.expect(wrapped); - try testing.expect((try a.to(u17)) == 1); + try testing.expect((try a.toInt(u17)) == 1); } test "addWrap multi-multi, unsigned, limb aligned" { @@ -639,7 +639,7 @@ test "addWrap multi-multi, unsigned, limb aligned" { const wrapped = try a.addWrap(&a, &b, .unsigned, @bitSizeOf(DoubleLimb)); try testing.expect(wrapped); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) - 1); } test "subWrap single-multi, unsigned, limb aligned" { @@ -652,7 +652,7 @@ test "subWrap single-multi, unsigned, limb aligned" { const wrapped = try a.subWrap(&a, &b, .unsigned, @bitSizeOf(DoubleLimb)); try testing.expect(wrapped); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) - 88); } test "addWrap single-single, signed" { @@ -665,7 +665,7 @@ test "addWrap single-single, signed" { const wrapped = try a.addWrap(&a, &b, .signed, @bitSizeOf(i21)); try testing.expect(wrapped); - try testing.expect((try a.to(i21)) == minInt(i21)); + try testing.expect((try a.toInt(i21)) == minInt(i21)); } test "subWrap single-single, signed" { @@ -678,7 +678,7 @@ test "subWrap single-single, signed" { const wrapped = try a.subWrap(&a, &b, .signed, @bitSizeOf(i21)); try testing.expect(wrapped); - try testing.expect((try a.to(i21)) == maxInt(i21)); + try testing.expect((try a.toInt(i21)) == maxInt(i21)); } test "addWrap multi-multi, signed, limb aligned" { @@ -691,7 +691,7 @@ test "addWrap multi-multi, signed, limb aligned" { const wrapped = try a.addWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb)); try testing.expect(wrapped); - try testing.expect((try a.to(SignedDoubleLimb)) == -2); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -2); } test "subWrap single-multi, signed, limb aligned" { @@ -704,7 +704,7 @@ test "subWrap single-multi, signed, limb aligned" { const wrapped = try a.subWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb)); try testing.expect(wrapped); - try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); } test "addSat single-single, unsigned" { @@ -716,7 +716,7 @@ test "addSat single-single, unsigned" { try a.addSat(&a, &b, .unsigned, 17); - try testing.expect((try a.to(u17)) == maxInt(u17)); + try testing.expect((try a.toInt(u17)) == maxInt(u17)); } test "subSat single-single, unsigned" { @@ -728,7 +728,7 @@ test "subSat single-single, unsigned" { try a.subSat(&a, &b, .unsigned, 17); - try testing.expect((try a.to(u17)) == 0); + try testing.expect((try a.toInt(u17)) == 0); } test "addSat multi-multi, unsigned, limb aligned" { @@ -740,7 +740,7 @@ test "addSat multi-multi, unsigned, limb aligned" { try a.addSat(&a, &b, .unsigned, @bitSizeOf(DoubleLimb)); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb)); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb)); } test "subSat single-multi, unsigned, limb aligned" { @@ -752,7 +752,7 @@ test "subSat single-multi, unsigned, limb aligned" { try a.subSat(&a, &b, .unsigned, @bitSizeOf(DoubleLimb)); - try testing.expect((try a.to(DoubleLimb)) == 0); + try testing.expect((try a.toInt(DoubleLimb)) == 0); } test "addSat single-single, signed" { @@ -764,7 +764,7 @@ test "addSat single-single, signed" { try a.addSat(&a, &b, .signed, @bitSizeOf(i14)); - try testing.expect((try a.to(i14)) == maxInt(i14)); + try testing.expect((try a.toInt(i14)) == maxInt(i14)); } test "subSat single-single, signed" { @@ -776,7 +776,7 @@ test "subSat single-single, signed" { try a.subSat(&a, &b, .signed, @bitSizeOf(i21)); - try testing.expect((try a.to(i21)) == minInt(i21)); + try testing.expect((try a.toInt(i21)) == minInt(i21)); } test "addSat multi-multi, signed, limb aligned" { @@ -788,7 +788,7 @@ test "addSat multi-multi, signed, limb aligned" { try a.addSat(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); } test "subSat single-multi, signed, limb aligned" { @@ -800,7 +800,7 @@ test "subSat single-multi, signed, limb aligned" { try a.subSat(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == minInt(SignedDoubleLimb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == minInt(SignedDoubleLimb)); } test "sub single-single" { @@ -813,7 +813,7 @@ test "sub single-single" { defer c.deinit(); try c.sub(&a, &b); - try testing.expect((try c.to(u32)) == 45); + try testing.expect((try c.toInt(u32)) == 45); } test "sub multi-single" { @@ -826,7 +826,7 @@ test "sub multi-single" { defer c.deinit(); try c.sub(&a, &b); - try testing.expect((try c.to(Limb)) == maxInt(Limb)); + try testing.expect((try c.toInt(Limb)) == maxInt(Limb)); } test "sub multi-multi" { @@ -843,7 +843,7 @@ test "sub multi-multi" { defer c.deinit(); try c.sub(&a, &b); - try testing.expect((try c.to(u128)) == op1 - op2); + try testing.expect((try c.toInt(u128)) == op1 - op2); } test "sub equal" { @@ -856,7 +856,7 @@ test "sub equal" { defer c.deinit(); try c.sub(&a, &b); - try testing.expect((try c.to(u32)) == 0); + try testing.expect((try c.toInt(u32)) == 0); } test "sub sign" { @@ -873,19 +873,19 @@ test "sub sign" { defer neg_two.deinit(); try a.sub(&one, &two); - try testing.expect((try a.to(i32)) == -1); + try testing.expect((try a.toInt(i32)) == -1); try a.sub(&neg_one, &two); - try testing.expect((try a.to(i32)) == -3); + try testing.expect((try a.toInt(i32)) == -3); try a.sub(&one, &neg_two); - try testing.expect((try a.to(i32)) == 3); + try testing.expect((try a.toInt(i32)) == 3); try a.sub(&neg_one, &neg_two); - try testing.expect((try a.to(i32)) == 1); + try testing.expect((try a.toInt(i32)) == 1); try a.sub(&neg_two, &neg_one); - try testing.expect((try a.to(i32)) == -1); + try testing.expect((try a.toInt(i32)) == -1); } test "mul single-single" { @@ -898,7 +898,7 @@ test "mul single-single" { defer c.deinit(); try c.mul(&a, &b); - try testing.expect((try c.to(u64)) == 250); + try testing.expect((try c.toInt(u64)) == 250); } test "mul multi-single" { @@ -911,7 +911,7 @@ test "mul multi-single" { defer c.deinit(); try c.mul(&a, &b); - try testing.expect((try c.to(DoubleLimb)) == 2 * maxInt(Limb)); + try testing.expect((try c.toInt(DoubleLimb)) == 2 * maxInt(Limb)); } test "mul multi-multi" { @@ -930,7 +930,7 @@ test "mul multi-multi" { defer c.deinit(); try c.mul(&a, &b); - try testing.expect((try c.to(u256)) == op1 * op2); + try testing.expect((try c.toInt(u256)) == op1 * op2); } test "mul alias r with a" { @@ -941,7 +941,7 @@ test "mul alias r with a" { try a.mul(&a, &b); - try testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb)); + try testing.expect((try a.toInt(DoubleLimb)) == 2 * maxInt(Limb)); } test "mul alias r with b" { @@ -952,7 +952,7 @@ test "mul alias r with b" { try a.mul(&b, &a); - try testing.expect((try a.to(DoubleLimb)) == 2 * maxInt(Limb)); + try testing.expect((try a.toInt(DoubleLimb)) == 2 * maxInt(Limb)); } test "mul alias r with a and b" { @@ -961,7 +961,7 @@ test "mul alias r with a and b" { try a.mul(&a, &a); - try testing.expect((try a.to(DoubleLimb)) == maxInt(Limb) * maxInt(Limb)); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(Limb) * maxInt(Limb)); } test "mul a*0" { @@ -974,7 +974,7 @@ test "mul a*0" { defer c.deinit(); try c.mul(&a, &b); - try testing.expect((try c.to(u32)) == 0); + try testing.expect((try c.toInt(u32)) == 0); } test "mul 0*0" { @@ -987,7 +987,7 @@ test "mul 0*0" { defer c.deinit(); try c.mul(&a, &b); - try testing.expect((try c.to(u32)) == 0); + try testing.expect((try c.toInt(u32)) == 0); } test "mul large" { @@ -1021,7 +1021,7 @@ test "mulWrap single-single unsigned" { defer c.deinit(); try c.mulWrap(&a, &b, .unsigned, 17); - try testing.expect((try c.to(u17)) == 59836); + try testing.expect((try c.toInt(u17)) == 59836); } test "mulWrap single-single signed" { @@ -1034,7 +1034,7 @@ test "mulWrap single-single signed" { defer c.deinit(); try c.mulWrap(&a, &b, .signed, 17); - try testing.expect((try c.to(i17)) == -59836); + try testing.expect((try c.toInt(i17)) == -59836); } test "mulWrap multi-multi unsigned" { @@ -1053,7 +1053,7 @@ test "mulWrap multi-multi unsigned" { defer c.deinit(); try c.mulWrap(&a, &b, .unsigned, 65); - try testing.expect((try c.to(u256)) == (op1 * op2) & ((1 << 65) - 1)); + try testing.expect((try c.toInt(u256)) == (op1 * op2) & ((1 << 65) - 1)); } test "mulWrap multi-multi signed" { @@ -1071,7 +1071,7 @@ test "mulWrap multi-multi signed" { defer c.deinit(); try c.mulWrap(&a, &b, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try c.to(SignedDoubleLimb)) == minInt(SignedDoubleLimb) + 2); + try testing.expect((try c.toInt(SignedDoubleLimb)) == minInt(SignedDoubleLimb) + 2); } test "mulWrap large" { @@ -1110,8 +1110,8 @@ test "div single-half no rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u32)) == 10); - try testing.expect((try r.to(u32)) == 0); + try testing.expect((try q.toInt(u32)) == 10); + try testing.expect((try r.toInt(u32)) == 0); } test "div single-half with rem" { @@ -1126,8 +1126,8 @@ test "div single-half with rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u32)) == 9); - try testing.expect((try r.to(u32)) == 4); + try testing.expect((try q.toInt(u32)) == 9); + try testing.expect((try r.toInt(u32)) == 4); } test "div single-single no rem" { @@ -1143,8 +1143,8 @@ test "div single-single no rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u32)) == 131072); - try testing.expect((try r.to(u32)) == 0); + try testing.expect((try q.toInt(u32)) == 131072); + try testing.expect((try r.toInt(u32)) == 0); } test "div single-single with rem" { @@ -1159,8 +1159,8 @@ test "div single-single with rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u64)) == 131072); - try testing.expect((try r.to(u64)) == 8589934592); + try testing.expect((try q.toInt(u64)) == 131072); + try testing.expect((try r.toInt(u64)) == 8589934592); } test "div multi-single no rem" { @@ -1179,8 +1179,8 @@ test "div multi-single no rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u64)) == op1 / op2); - try testing.expect((try r.to(u64)) == 0); + try testing.expect((try q.toInt(u64)) == op1 / op2); + try testing.expect((try r.toInt(u64)) == 0); } test "div multi-single with rem" { @@ -1199,8 +1199,8 @@ test "div multi-single with rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u64)) == op1 / op2); - try testing.expect((try r.to(u64)) == 3); + try testing.expect((try q.toInt(u64)) == op1 / op2); + try testing.expect((try r.toInt(u64)) == 3); } test "div multi>2-single" { @@ -1219,8 +1219,8 @@ test "div multi>2-single" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == op1 / op2); - try testing.expect((try r.to(u32)) == 0x3e4e); + try testing.expect((try q.toInt(u128)) == op1 / op2); + try testing.expect((try r.toInt(u32)) == 0x3e4e); } test "div single-single q < r" { @@ -1235,8 +1235,8 @@ test "div single-single q < r" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u64)) == 0); - try testing.expect((try r.to(u64)) == 0x0078f432); + try testing.expect((try q.toInt(u64)) == 0); + try testing.expect((try r.toInt(u64)) == 0x0078f432); } test "div single-single q == r" { @@ -1251,8 +1251,8 @@ test "div single-single q == r" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u64)) == 1); - try testing.expect((try r.to(u64)) == 0); + try testing.expect((try q.toInt(u64)) == 1); + try testing.expect((try r.toInt(u64)) == 0); } test "div q=0 alias" { @@ -1263,8 +1263,8 @@ test "div q=0 alias" { try Managed.divTrunc(&a, &b, &a, &b); - try testing.expect((try a.to(u64)) == 0); - try testing.expect((try b.to(u64)) == 3); + try testing.expect((try a.toInt(u64)) == 0); + try testing.expect((try b.toInt(u64)) == 3); } test "div multi-multi q < r" { @@ -1283,8 +1283,8 @@ test "div multi-multi q < r" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0); - try testing.expect((try r.to(u128)) == op1); + try testing.expect((try q.toInt(u128)) == 0); + try testing.expect((try r.toInt(u128)) == op1); } test "div trunc single-single +/+" { @@ -1307,8 +1307,8 @@ test "div trunc single-single +/+" { const eq = @divTrunc(u, v); const er = @mod(u, v); - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div trunc single-single -/+" { @@ -1331,8 +1331,8 @@ test "div trunc single-single -/+" { const eq = -1; const er = -2; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div trunc single-single +/-" { @@ -1355,8 +1355,8 @@ test "div trunc single-single +/-" { const eq = -1; const er = 2; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div trunc single-single -/-" { @@ -1379,8 +1379,8 @@ test "div trunc single-single -/-" { const eq = 1; const er = -2; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "divTrunc #15535" { @@ -1417,7 +1417,7 @@ test "divFloor #10932" { const ress = try res.toString(testing.allocator, 16, .lower); defer testing.allocator.free(ress); try testing.expect(std.mem.eql(u8, ress, "194bd136316c046d070b763396297bf8869a605030216b52597015902a172b2a752f62af1568dcd431602f03725bfa62b0be71ae86616210972c0126e173503011ca48c5747ff066d159c95e46b69cbb14c8fc0bd2bf0919f921be96463200000000000000000000000000000000000000000000000000000000000000000000000000000000")); - try testing.expect((try mod.to(i32)) == 0); + try testing.expect((try mod.toInt(i32)) == 0); } test "divFloor #11166" { @@ -1482,7 +1482,7 @@ test "bitAnd #10932" { try res.bitAnd(&a, &b); - try testing.expect((try res.to(i32)) == 0); + try testing.expect((try res.toInt(i32)) == 0); } test "bit And #19235" { @@ -1495,7 +1495,7 @@ test "bit And #19235" { try r.bitAnd(&a, &b); - try testing.expect((try r.to(i128)) == 0x10000000000000000); + try testing.expect((try r.toInt(i128)) == 0x10000000000000000); } test "div floor single-single +/+" { @@ -1518,8 +1518,8 @@ test "div floor single-single +/+" { const eq = 1; const er = 2; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div floor single-single -/+" { @@ -1542,8 +1542,8 @@ test "div floor single-single -/+" { const eq = -2; const er = 1; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div floor single-single +/-" { @@ -1566,8 +1566,8 @@ test "div floor single-single +/-" { const eq = -2; const er = -1; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div floor single-single -/-" { @@ -1590,8 +1590,8 @@ test "div floor single-single -/-" { const eq = 1; const er = -2; - try testing.expect((try q.to(i32)) == eq); - try testing.expect((try r.to(i32)) == er); + try testing.expect((try q.toInt(i32)) == eq); + try testing.expect((try r.toInt(i32)) == er); } test "div floor no remainder negative quotient" { @@ -1609,8 +1609,8 @@ test "div floor no remainder negative quotient" { defer r.deinit(); try Managed.divFloor(&q, &r, &a, &b); - try testing.expect((try q.to(i32)) == -0x80000000); - try testing.expect((try r.to(i32)) == 0); + try testing.expect((try q.toInt(i32)) == -0x80000000); + try testing.expect((try r.toInt(i32)) == 0); } test "div floor negative close to zero" { @@ -1628,8 +1628,8 @@ test "div floor negative close to zero" { defer r.deinit(); try Managed.divFloor(&q, &r, &a, &b); - try testing.expect((try q.to(i32)) == -1); - try testing.expect((try r.to(i32)) == 10); + try testing.expect((try q.toInt(i32)) == -1); + try testing.expect((try r.toInt(i32)) == 10); } test "div floor positive close to zero" { @@ -1647,8 +1647,8 @@ test "div floor positive close to zero" { defer r.deinit(); try Managed.divFloor(&q, &r, &a, &b); - try testing.expect((try q.to(i32)) == 0); - try testing.expect((try r.to(i32)) == 10); + try testing.expect((try q.toInt(i32)) == 0); + try testing.expect((try r.toInt(i32)) == 10); } test "div multi-multi with rem" { @@ -1665,8 +1665,8 @@ test "div multi-multi with rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b); - try testing.expect((try r.to(u128)) == 0x28de0acacd806823638); + try testing.expect((try q.toInt(u128)) == 0xe38f38e39161aaabd03f0f1b); + try testing.expect((try r.toInt(u128)) == 0x28de0acacd806823638); } test "div multi-multi no rem" { @@ -1683,8 +1683,8 @@ test "div multi-multi no rem" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0xe38f38e39161aaabd03f0f1b); - try testing.expect((try r.to(u128)) == 0); + try testing.expect((try q.toInt(u128)) == 0xe38f38e39161aaabd03f0f1b); + try testing.expect((try r.toInt(u128)) == 0); } test "div multi-multi (2 branch)" { @@ -1701,8 +1701,8 @@ test "div multi-multi (2 branch)" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0x10000000000000000); - try testing.expect((try r.to(u128)) == 0x44444443444444431111111111111111); + try testing.expect((try q.toInt(u128)) == 0x10000000000000000); + try testing.expect((try r.toInt(u128)) == 0x44444443444444431111111111111111); } test "div multi-multi (3.1/3.3 branch)" { @@ -1719,8 +1719,8 @@ test "div multi-multi (3.1/3.3 branch)" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0xfffffffffffffffffff); - try testing.expect((try r.to(u256)) == 0x1111111111111111111110b12222222222222222282); + try testing.expect((try q.toInt(u128)) == 0xfffffffffffffffffff); + try testing.expect((try r.toInt(u256)) == 0x1111111111111111111110b12222222222222222282); } test "div multi-single zero-limb trailing" { @@ -1757,7 +1757,7 @@ test "div multi-multi zero-limb trailing (with rem)" { defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0x10000000000000000); + try testing.expect((try q.toInt(u128)) == 0x10000000000000000); const rs = try r.toString(testing.allocator, 16, .lower); defer testing.allocator.free(rs); @@ -1778,7 +1778,7 @@ test "div multi-multi zero-limb trailing (with rem) and dividend zero-limb count defer r.deinit(); try Managed.divTrunc(&q, &r, &a, &b); - try testing.expect((try q.to(u128)) == 0x1); + try testing.expect((try q.toInt(u128)) == 0x1); const rs = try r.toString(testing.allocator, 16, .lower); defer testing.allocator.free(rs); @@ -1862,7 +1862,7 @@ test "truncate single unsigned" { try a.truncate(&a, .unsigned, 17); - try testing.expect((try a.to(u17)) == maxInt(u17)); + try testing.expect((try a.toInt(u17)) == maxInt(u17)); } test "truncate single signed" { @@ -1871,7 +1871,7 @@ test "truncate single signed" { try a.truncate(&a, .signed, 17); - try testing.expect((try a.to(i17)) == minInt(i17)); + try testing.expect((try a.toInt(i17)) == minInt(i17)); } test "truncate multi to single unsigned" { @@ -1880,7 +1880,7 @@ test "truncate multi to single unsigned" { try a.truncate(&a, .unsigned, 27); - try testing.expect((try a.to(u27)) == 0x2BC_DEF0); + try testing.expect((try a.toInt(u27)) == 0x2BC_DEF0); } test "truncate multi to single signed" { @@ -1889,7 +1889,7 @@ test "truncate multi to single signed" { try a.truncate(&a, .signed, @bitSizeOf(i11)); - try testing.expect((try a.to(i11)) == minInt(i11)); + try testing.expect((try a.toInt(i11)) == minInt(i11)); } test "truncate multi to multi unsigned" { @@ -1901,7 +1901,7 @@ test "truncate multi to multi unsigned" { try a.truncate(&a, .unsigned, bits - 1); - try testing.expect((try a.to(Int)) == maxInt(Int)); + try testing.expect((try a.toInt(Int)) == maxInt(Int)); } test "truncate multi to multi signed" { @@ -1910,7 +1910,7 @@ test "truncate multi to multi signed" { try a.truncate(&a, .signed, @bitSizeOf(Limb) + 1); - try testing.expect((try a.to(std.meta.Int(.signed, @bitSizeOf(Limb) + 1))) == -1 << @bitSizeOf(Limb)); + try testing.expect((try a.toInt(std.meta.Int(.signed, @bitSizeOf(Limb) + 1))) == -1 << @bitSizeOf(Limb)); } test "truncate negative multi to single" { @@ -1919,7 +1919,7 @@ test "truncate negative multi to single" { try a.truncate(&a, .signed, @bitSizeOf(i17)); - try testing.expect((try a.to(i17)) == 0); + try testing.expect((try a.toInt(i17)) == 0); } test "truncate multi unsigned many" { @@ -1931,7 +1931,7 @@ test "truncate multi unsigned many" { defer b.deinit(); try b.truncate(&a, .signed, @bitSizeOf(i1)); - try testing.expect((try b.to(i1)) == 0); + try testing.expect((try b.toInt(i1)) == 0); } test "saturate single signed positive" { @@ -1940,7 +1940,7 @@ test "saturate single signed positive" { try a.saturate(&a, .signed, 17); - try testing.expect((try a.to(i17)) == maxInt(i17)); + try testing.expect((try a.toInt(i17)) == maxInt(i17)); } test "saturate single signed negative" { @@ -1949,7 +1949,7 @@ test "saturate single signed negative" { try a.saturate(&a, .signed, 17); - try testing.expect((try a.to(i17)) == minInt(i17)); + try testing.expect((try a.toInt(i17)) == minInt(i17)); } test "saturate single signed" { @@ -1958,7 +1958,7 @@ test "saturate single signed" { try a.saturate(&a, .signed, 17); - try testing.expect((try a.to(i17)) == maxInt(i17) - 1); + try testing.expect((try a.toInt(i17)) == maxInt(i17) - 1); } test "saturate multi signed" { @@ -1967,7 +1967,7 @@ test "saturate multi signed" { try a.saturate(&a, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); } test "saturate single unsigned" { @@ -1976,7 +1976,7 @@ test "saturate single unsigned" { try a.saturate(&a, .unsigned, 23); - try testing.expect((try a.to(u23)) == maxInt(u23)); + try testing.expect((try a.toInt(u23)) == maxInt(u23)); } test "saturate multi unsigned zero" { @@ -1994,7 +1994,7 @@ test "saturate multi unsigned" { try a.saturate(&a, .unsigned, @bitSizeOf(DoubleLimb)); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb)); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb)); } test "shift-right single" { @@ -2002,7 +2002,7 @@ test "shift-right single" { defer a.deinit(); try a.shiftRight(&a, 16); - try testing.expect((try a.to(u32)) == 0xffff); + try testing.expect((try a.toInt(u32)) == 0xffff); } test "shift-right multi" { @@ -2010,7 +2010,7 @@ test "shift-right multi" { defer a.deinit(); try a.shiftRight(&a, 67); - try testing.expect((try a.to(u64)) == 0x1fffe0001dddc222); + try testing.expect((try a.toInt(u64)) == 0x1fffe0001dddc222); try a.set(0xffff0000eeee1111dddd2222cccc3333); try a.shiftRight(&a, 63); @@ -2037,7 +2037,7 @@ test "shift-left single" { defer a.deinit(); try a.shiftLeft(&a, 16); - try testing.expect((try a.to(u64)) == 0xffff0000); + try testing.expect((try a.toInt(u64)) == 0xffff0000); } test "shift-left multi" { @@ -2045,7 +2045,7 @@ test "shift-left multi" { defer a.deinit(); try a.shiftLeft(&a, 67); - try testing.expect((try a.to(u128)) == 0xffff0000eeee11100000000000000000); + try testing.expect((try a.toInt(u128)) == 0xffff0000eeee11100000000000000000); } test "shift-right negative" { @@ -2055,43 +2055,43 @@ test "shift-right negative" { var arg = try Managed.initSet(testing.allocator, -20); defer arg.deinit(); try a.shiftRight(&arg, 2); - try testing.expect((try a.to(i32)) == -5); // -20 >> 2 == -5 + try testing.expect((try a.toInt(i32)) == -5); // -20 >> 2 == -5 var arg2 = try Managed.initSet(testing.allocator, -5); defer arg2.deinit(); try a.shiftRight(&arg2, 10); - try testing.expect((try a.to(i32)) == -1); // -5 >> 10 == -1 + try testing.expect((try a.toInt(i32)) == -1); // -5 >> 10 == -1 var arg3 = try Managed.initSet(testing.allocator, -10); defer arg3.deinit(); try a.shiftRight(&arg3, 1232); - try testing.expect((try a.to(i32)) == -1); // -10 >> 1232 == -1 + try testing.expect((try a.toInt(i32)) == -1); // -10 >> 1232 == -1 var arg4 = try Managed.initSet(testing.allocator, -5); defer arg4.deinit(); try a.shiftRight(&arg4, 2); - try testing.expect(try a.to(i32) == -2); // -5 >> 2 == -2 + try testing.expect(try a.toInt(i32) == -2); // -5 >> 2 == -2 var arg5 = try Managed.initSet(testing.allocator, -0xffff0000eeee1111dddd2222cccc3333); defer arg5.deinit(); try a.shiftRight(&arg5, 67); - try testing.expect(try a.to(i64) == -0x1fffe0001dddc223); + try testing.expect(try a.toInt(i64) == -0x1fffe0001dddc223); var arg6 = try Managed.initSet(testing.allocator, -0x1ffffffffffffffff); defer arg6.deinit(); try a.shiftRight(&arg6, 1); try a.shiftRight(&a, 1); a.setSign(true); - try testing.expect(try a.to(u64) == 0x8000000000000000); + try testing.expect(try a.toInt(u64) == 0x8000000000000000); var arg7 = try Managed.initSet(testing.allocator, -32767); defer arg7.deinit(); a.setSign(false); try a.shiftRight(&arg7, 4); - try testing.expect(try a.to(i16) == -2048); + try testing.expect(try a.toInt(i16) == -2048); a.setSign(true); try a.shiftRight(&arg7, 4); - try testing.expect(try a.to(i16) == -2048); + try testing.expect(try a.toInt(i16) == -2048); } test "sat shift-left simple unsigned" { @@ -2099,7 +2099,7 @@ test "sat shift-left simple unsigned" { defer a.deinit(); try a.shiftLeftSat(&a, 16, .unsigned, 21); - try testing.expect((try a.to(u64)) == 0x1fffff); + try testing.expect((try a.toInt(u64)) == 0x1fffff); } test "sat shift-left simple unsigned no sat" { @@ -2107,7 +2107,7 @@ test "sat shift-left simple unsigned no sat" { defer a.deinit(); try a.shiftLeftSat(&a, 16, .unsigned, 21); - try testing.expect((try a.to(u64)) == 0x10000); + try testing.expect((try a.toInt(u64)) == 0x10000); } test "sat shift-left multi unsigned" { @@ -2115,7 +2115,7 @@ test "sat shift-left multi unsigned" { defer a.deinit(); try a.shiftLeftSat(&a, @bitSizeOf(DoubleLimb) - 3, .unsigned, @bitSizeOf(DoubleLimb) - 1); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) >> 1); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb) >> 1); } test "sat shift-left unsigned shift > bitcount" { @@ -2123,7 +2123,7 @@ test "sat shift-left unsigned shift > bitcount" { defer a.deinit(); try a.shiftLeftSat(&a, 10, .unsigned, 10); - try testing.expect((try a.to(u10)) == maxInt(u10)); + try testing.expect((try a.toInt(u10)) == maxInt(u10)); } test "sat shift-left unsigned zero" { @@ -2131,7 +2131,7 @@ test "sat shift-left unsigned zero" { defer a.deinit(); try a.shiftLeftSat(&a, 1, .unsigned, 0); - try testing.expect((try a.to(u64)) == 0); + try testing.expect((try a.toInt(u64)) == 0); } test "sat shift-left unsigned negative" { @@ -2139,7 +2139,7 @@ test "sat shift-left unsigned negative" { defer a.deinit(); try a.shiftLeftSat(&a, 0, .unsigned, 0); - try testing.expect((try a.to(u64)) == 0); + try testing.expect((try a.toInt(u64)) == 0); } test "sat shift-left signed simple negative" { @@ -2147,7 +2147,7 @@ test "sat shift-left signed simple negative" { defer a.deinit(); try a.shiftLeftSat(&a, 3, .signed, 10); - try testing.expect((try a.to(i10)) == minInt(i10)); + try testing.expect((try a.toInt(i10)) == minInt(i10)); } test "sat shift-left signed simple positive" { @@ -2155,7 +2155,7 @@ test "sat shift-left signed simple positive" { defer a.deinit(); try a.shiftLeftSat(&a, 3, .signed, 10); - try testing.expect((try a.to(i10)) == maxInt(i10)); + try testing.expect((try a.toInt(i10)) == maxInt(i10)); } test "sat shift-left signed multi positive" { @@ -2170,7 +2170,7 @@ test "sat shift-left signed multi positive" { defer a.deinit(); try a.shiftLeftSat(&a, shift, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == x <<| shift); + try testing.expect((try a.toInt(SignedDoubleLimb)) == x <<| shift); } test "sat shift-left signed multi negative" { @@ -2185,7 +2185,7 @@ test "sat shift-left signed multi negative" { defer a.deinit(); try a.shiftLeftSat(&a, shift, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == x <<| shift); + try testing.expect((try a.toInt(SignedDoubleLimb)) == x <<| shift); } test "bitNotWrap unsigned simple" { @@ -2197,7 +2197,7 @@ test "bitNotWrap unsigned simple" { try a.bitNotWrap(&a, .unsigned, 10); - try testing.expect((try a.to(u10)) == ~x); + try testing.expect((try a.toInt(u10)) == ~x); } test "bitNotWrap unsigned multi" { @@ -2206,7 +2206,7 @@ test "bitNotWrap unsigned multi" { try a.bitNotWrap(&a, .unsigned, @bitSizeOf(DoubleLimb)); - try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb)); + try testing.expect((try a.toInt(DoubleLimb)) == maxInt(DoubleLimb)); } test "bitNotWrap signed simple" { @@ -2218,7 +2218,7 @@ test "bitNotWrap signed simple" { try a.bitNotWrap(&a, .signed, 11); - try testing.expect((try a.to(i11)) == ~x); + try testing.expect((try a.toInt(i11)) == ~x); } test "bitNotWrap signed multi" { @@ -2227,7 +2227,7 @@ test "bitNotWrap signed multi" { try a.bitNotWrap(&a, .signed, @bitSizeOf(SignedDoubleLimb)); - try testing.expect((try a.to(SignedDoubleLimb)) == -1); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -1); } test "bitNotWrap more than two limbs" { @@ -2249,11 +2249,11 @@ test "bitNotWrap more than two limbs" { try res.bitNotWrap(&a, .unsigned, bits); const Unsigned = @Type(.{ .int = .{ .signedness = .unsigned, .bits = bits } }); - try testing.expectEqual((try res.to(Unsigned)), ~@as(Unsigned, maxInt(Limb))); + try testing.expectEqual((try res.toInt(Unsigned)), ~@as(Unsigned, maxInt(Limb))); try res.bitNotWrap(&a, .signed, bits); const Signed = @Type(.{ .int = .{ .signedness = .signed, .bits = bits } }); - try testing.expectEqual((try res.to(Signed)), ~@as(Signed, maxInt(Limb))); + try testing.expectEqual((try res.toInt(Signed)), ~@as(Signed, maxInt(Limb))); } test "bitwise and simple" { @@ -2264,7 +2264,7 @@ test "bitwise and simple" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(u64)) == 0xeeeeeeee00000000); + try testing.expect((try a.toInt(u64)) == 0xeeeeeeee00000000); } test "bitwise and multi-limb" { @@ -2275,7 +2275,7 @@ test "bitwise and multi-limb" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(u128)) == 0); + try testing.expect((try a.toInt(u128)) == 0); } test "bitwise and negative-positive simple" { @@ -2286,7 +2286,7 @@ test "bitwise and negative-positive simple" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(u64)) == 0x22222222); + try testing.expect((try a.toInt(u64)) == 0x22222222); } test "bitwise and negative-positive multi-limb" { @@ -2308,7 +2308,7 @@ test "bitwise and positive-negative simple" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(u64)) == 0x1111111111111110); + try testing.expect((try a.toInt(u64)) == 0x1111111111111110); } test "bitwise and positive-negative multi-limb" { @@ -2330,7 +2330,7 @@ test "bitwise and negative-negative simple" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(i128)) == -0xffffffff33333332); + try testing.expect((try a.toInt(i128)) == -0xffffffff33333332); } test "bitwise and negative-negative multi-limb" { @@ -2341,7 +2341,7 @@ test "bitwise and negative-negative multi-limb" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(i128)) == -maxInt(Limb) * 2 - 2); + try testing.expect((try a.toInt(i128)) == -maxInt(Limb) * 2 - 2); } test "bitwise and negative overflow" { @@ -2352,7 +2352,7 @@ test "bitwise and negative overflow" { try a.bitAnd(&a, &b); - try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb) - 1); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb) - 1); } test "bitwise xor simple" { @@ -2363,7 +2363,7 @@ test "bitwise xor simple" { try a.bitXor(&a, &b); - try testing.expect((try a.to(u64)) == 0x1111111133333333); + try testing.expect((try a.toInt(u64)) == 0x1111111133333333); } test "bitwise xor multi-limb" { @@ -2378,7 +2378,7 @@ test "bitwise xor multi-limb" { try a.bitXor(&a, &b); - try testing.expect((try a.to(DoubleLimb)) == x ^ y); + try testing.expect((try a.toInt(DoubleLimb)) == x ^ y); } test "bitwise xor single negative simple" { @@ -2389,7 +2389,7 @@ test "bitwise xor single negative simple" { try a.bitXor(&a, &b); - try testing.expect((try a.to(i64)) == -0x2efed94fcb932ef9); + try testing.expect((try a.toInt(i64)) == -0x2efed94fcb932ef9); } test "bitwise xor single negative multi-limb" { @@ -2400,7 +2400,7 @@ test "bitwise xor single negative multi-limb" { try a.bitXor(&a, &b); - try testing.expect((try a.to(i128)) == -0x6a50889abd8834a24db1f19650d3999a); + try testing.expect((try a.toInt(i128)) == -0x6a50889abd8834a24db1f19650d3999a); } test "bitwise xor single negative overflow" { @@ -2411,7 +2411,7 @@ test "bitwise xor single negative overflow" { try a.bitXor(&a, &b); - try testing.expect((try a.to(SignedDoubleLimb)) == -(maxInt(Limb) + 1)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -(maxInt(Limb) + 1)); } test "bitwise xor double negative simple" { @@ -2422,7 +2422,7 @@ test "bitwise xor double negative simple" { try a.bitXor(&a, &b); - try testing.expect((try a.to(u64)) == 0xc39c47081a6eb759); + try testing.expect((try a.toInt(u64)) == 0xc39c47081a6eb759); } test "bitwise xor double negative multi-limb" { @@ -2433,7 +2433,7 @@ test "bitwise xor double negative multi-limb" { try a.bitXor(&a, &b); - try testing.expect((try a.to(u128)) == 0xa3492ec28e62c410dff92bf0549bf771); + try testing.expect((try a.toInt(u128)) == 0xa3492ec28e62c410dff92bf0549bf771); } test "bitwise or simple" { @@ -2444,7 +2444,7 @@ test "bitwise or simple" { try a.bitOr(&a, &b); - try testing.expect((try a.to(u64)) == 0xffffffff33333333); + try testing.expect((try a.toInt(u64)) == 0xffffffff33333333); } test "bitwise or multi-limb" { @@ -2455,7 +2455,7 @@ test "bitwise or multi-limb" { try a.bitOr(&a, &b); - try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb)); + try testing.expect((try a.toInt(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb)); } test "bitwise or negative-positive simple" { @@ -2466,7 +2466,7 @@ test "bitwise or negative-positive simple" { try a.bitOr(&a, &b); - try testing.expect((try a.to(i64)) == -0x1111111111111111); + try testing.expect((try a.toInt(i64)) == -0x1111111111111111); } test "bitwise or negative-positive multi-limb" { @@ -2477,7 +2477,7 @@ test "bitwise or negative-positive multi-limb" { try a.bitOr(&a, &b); - try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb)); } test "bitwise or positive-negative simple" { @@ -2488,7 +2488,7 @@ test "bitwise or positive-negative simple" { try a.bitOr(&a, &b); - try testing.expect((try a.to(i64)) == -0x22222221); + try testing.expect((try a.toInt(i64)) == -0x22222221); } test "bitwise or positive-negative multi-limb" { @@ -2499,7 +2499,7 @@ test "bitwise or positive-negative multi-limb" { try a.bitOr(&a, &b); - try testing.expect((try a.to(SignedDoubleLimb)) == -1); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -1); } test "bitwise or negative-negative simple" { @@ -2510,7 +2510,7 @@ test "bitwise or negative-negative simple" { try a.bitOr(&a, &b); - try testing.expect((try a.to(i128)) == -0xeeeeeeee00000001); + try testing.expect((try a.toInt(i128)) == -0xeeeeeeee00000001); } test "bitwise or negative-negative multi-limb" { @@ -2521,7 +2521,7 @@ test "bitwise or negative-negative multi-limb" { try a.bitOr(&a, &b); - try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb)); + try testing.expect((try a.toInt(SignedDoubleLimb)) == -maxInt(Limb)); } test "var args" { @@ -2531,7 +2531,7 @@ test "var args" { var b = try Managed.initSet(testing.allocator, 6); defer b.deinit(); try a.add(&a, &b); - try testing.expect((try a.to(u64)) == 11); + try testing.expect((try a.toInt(u64)) == 11); var c = try Managed.initSet(testing.allocator, 11); defer c.deinit(); @@ -2552,7 +2552,7 @@ test "gcd non-one small" { try r.gcd(&a, &b); - try testing.expect((try r.to(u32)) == 1); + try testing.expect((try r.toInt(u32)) == 1); } test "gcd non-one medium" { @@ -2565,7 +2565,7 @@ test "gcd non-one medium" { try r.gcd(&a, &b); - try testing.expect((try r.to(u32)) == 38); + try testing.expect((try r.toInt(u32)) == 38); } test "gcd non-one large" { @@ -2578,7 +2578,7 @@ test "gcd non-one large" { try r.gcd(&a, &b); - try testing.expect((try r.to(u32)) == 4369); + try testing.expect((try r.toInt(u32)) == 4369); } test "gcd large multi-limb result" { @@ -2593,7 +2593,7 @@ test "gcd large multi-limb result" { try r.gcd(&a, &b); - const answer = (try r.to(u256)); + const answer = (try r.toInt(u256)); try testing.expect(answer == 0xf000000ff00000fff0000ffff000fffff00ffffff1); } @@ -2607,7 +2607,7 @@ test "gcd one large" { try r.gcd(&a, &b); - try testing.expect((try r.to(u64)) == 1); + try testing.expect((try r.toInt(u64)) == 1); } test "mutable to managed" { @@ -2637,10 +2637,10 @@ test "pow" { defer a.deinit(); try a.pow(&a, 3); - try testing.expectEqual(@as(i32, -27), try a.to(i32)); + try testing.expectEqual(@as(i32, -27), try a.toInt(i32)); try a.pow(&a, 4); - try testing.expectEqual(@as(i32, 531441), try a.to(i32)); + try testing.expectEqual(@as(i32, 531441), try a.toInt(i32)); } { var a = try Managed.initSet(testing.allocator, 10); @@ -2671,18 +2671,18 @@ test "pow" { defer a.deinit(); try a.pow(&a, 100); - try testing.expectEqual(@as(i32, 0), try a.to(i32)); + try testing.expectEqual(@as(i32, 0), try a.toInt(i32)); try a.set(1); try a.pow(&a, 0); - try testing.expectEqual(@as(i32, 1), try a.to(i32)); + try testing.expectEqual(@as(i32, 1), try a.toInt(i32)); try a.pow(&a, 100); - try testing.expectEqual(@as(i32, 1), try a.to(i32)); + try testing.expectEqual(@as(i32, 1), try a.toInt(i32)); try a.set(-1); try a.pow(&a, 15); - try testing.expectEqual(@as(i32, -1), try a.to(i32)); + try testing.expectEqual(@as(i32, -1), try a.toInt(i32)); try a.pow(&a, 16); - try testing.expectEqual(@as(i32, 1), try a.to(i32)); + try testing.expectEqual(@as(i32, 1), try a.toInt(i32)); } } @@ -2696,24 +2696,24 @@ test "sqrt" { try r.set(0); try a.set(25); try r.sqrt(&a); - try testing.expectEqual(@as(i32, 5), try r.to(i32)); + try testing.expectEqual(@as(i32, 5), try r.toInt(i32)); // aliased try a.set(25); try a.sqrt(&a); - try testing.expectEqual(@as(i32, 5), try a.to(i32)); + try testing.expectEqual(@as(i32, 5), try a.toInt(i32)); // bottom try r.set(0); try a.set(24); try r.sqrt(&a); - try testing.expectEqual(@as(i32, 4), try r.to(i32)); + try testing.expectEqual(@as(i32, 4), try r.toInt(i32)); // large number try r.set(0); try a.set(0x1_0000_0000_0000); try r.sqrt(&a); - try testing.expectEqual(@as(i32, 0x100_0000), try r.to(i32)); + try testing.expectEqual(@as(i32, 0x100_0000), try r.toInt(i32)); } test "regression test for 1 limb overflow with alias" { @@ -3225,7 +3225,7 @@ test "Managed sqrt(0) = 0" { try a.setString(10, "0"); try res.sqrt(&a); - try testing.expectEqual(@as(i32, 0), try res.to(i32)); + try testing.expectEqual(@as(i32, 0), try res.toInt(i32)); } test "Managed sqrt(-1) = error" { diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig index ce93f40a25f2..08a2c2338837 100644 --- a/lib/std/math/big/rational.zig +++ b/lib/std/math/big/rational.zig @@ -518,28 +518,28 @@ test "set" { defer a.deinit(); try a.setInt(5); - try testing.expect((try a.p.to(u32)) == 5); - try testing.expect((try a.q.to(u32)) == 1); + try testing.expect((try a.p.toInt(u32)) == 5); + try testing.expect((try a.q.toInt(u32)) == 1); try a.setRatio(7, 3); - try testing.expect((try a.p.to(u32)) == 7); - try testing.expect((try a.q.to(u32)) == 3); + try testing.expect((try a.p.toInt(u32)) == 7); + try testing.expect((try a.q.toInt(u32)) == 3); try a.setRatio(9, 3); - try testing.expect((try a.p.to(i32)) == 3); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == 3); + try testing.expect((try a.q.toInt(i32)) == 1); try a.setRatio(-9, 3); - try testing.expect((try a.p.to(i32)) == -3); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == -3); + try testing.expect((try a.q.toInt(i32)) == 1); try a.setRatio(9, -3); - try testing.expect((try a.p.to(i32)) == -3); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == -3); + try testing.expect((try a.q.toInt(i32)) == 1); try a.setRatio(-9, -3); - try testing.expect((try a.p.to(i32)) == 3); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == 3); + try testing.expect((try a.q.toInt(i32)) == 1); } test "setFloat" { @@ -547,24 +547,24 @@ test "setFloat" { defer a.deinit(); try a.setFloat(f64, 2.5); - try testing.expect((try a.p.to(i32)) == 5); - try testing.expect((try a.q.to(i32)) == 2); + try testing.expect((try a.p.toInt(i32)) == 5); + try testing.expect((try a.q.toInt(i32)) == 2); try a.setFloat(f32, -2.5); - try testing.expect((try a.p.to(i32)) == -5); - try testing.expect((try a.q.to(i32)) == 2); + try testing.expect((try a.p.toInt(i32)) == -5); + try testing.expect((try a.q.toInt(i32)) == 2); try a.setFloat(f32, 3.141593); // = 3.14159297943115234375 - try testing.expect((try a.p.to(u32)) == 3294199); - try testing.expect((try a.q.to(u32)) == 1048576); + try testing.expect((try a.p.toInt(u32)) == 3294199); + try testing.expect((try a.q.toInt(u32)) == 1048576); try a.setFloat(f64, 72.141593120712409172417410926841290461290467124); // = 72.1415931207124145885245525278151035308837890625 - try testing.expect((try a.p.to(u128)) == 5076513310880537); - try testing.expect((try a.q.to(u128)) == 70368744177664); + try testing.expect((try a.p.toInt(u128)) == 5076513310880537); + try testing.expect((try a.q.toInt(u128)) == 70368744177664); } test "setFloatString" { @@ -574,8 +574,8 @@ test "setFloatString" { try a.setFloatString("72.14159312071241458852455252781510353"); // = 72.1415931207124145885245525278151035308837890625 - try testing.expect((try a.p.to(u128)) == 7214159312071241458852455252781510353); - try testing.expect((try a.q.to(u128)) == 100000000000000000000000000000000000); + try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353); + try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000); } test "toFloat" { @@ -612,8 +612,8 @@ test "copy" { defer b.deinit(); try a.copyInt(b); - try testing.expect((try a.p.to(u32)) == 5); - try testing.expect((try a.q.to(u32)) == 1); + try testing.expect((try a.p.toInt(u32)) == 5); + try testing.expect((try a.q.toInt(u32)) == 1); var c = try Int.initSet(testing.allocator, 7); defer c.deinit(); @@ -621,8 +621,8 @@ test "copy" { defer d.deinit(); try a.copyRatio(c, d); - try testing.expect((try a.p.to(u32)) == 7); - try testing.expect((try a.q.to(u32)) == 3); + try testing.expect((try a.p.toInt(u32)) == 7); + try testing.expect((try a.q.toInt(u32)) == 3); var e = try Int.initSet(testing.allocator, 9); defer e.deinit(); @@ -630,8 +630,8 @@ test "copy" { defer f.deinit(); try a.copyRatio(e, f); - try testing.expect((try a.p.to(u32)) == 3); - try testing.expect((try a.q.to(u32)) == 1); + try testing.expect((try a.p.toInt(u32)) == 3); + try testing.expect((try a.q.toInt(u32)) == 1); } test "negate" { @@ -639,16 +639,16 @@ test "negate" { defer a.deinit(); try a.setInt(-50); - try testing.expect((try a.p.to(i32)) == -50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == -50); + try testing.expect((try a.q.toInt(i32)) == 1); a.negate(); - try testing.expect((try a.p.to(i32)) == 50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == 50); + try testing.expect((try a.q.toInt(i32)) == 1); a.negate(); - try testing.expect((try a.p.to(i32)) == -50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == -50); + try testing.expect((try a.q.toInt(i32)) == 1); } test "abs" { @@ -656,16 +656,16 @@ test "abs" { defer a.deinit(); try a.setInt(-50); - try testing.expect((try a.p.to(i32)) == -50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == -50); + try testing.expect((try a.q.toInt(i32)) == 1); a.abs(); - try testing.expect((try a.p.to(i32)) == 50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == 50); + try testing.expect((try a.q.toInt(i32)) == 1); a.abs(); - try testing.expect((try a.p.to(i32)) == 50); - try testing.expect((try a.q.to(i32)) == 1); + try testing.expect((try a.p.toInt(i32)) == 50); + try testing.expect((try a.q.toInt(i32)) == 1); } test "swap" { @@ -677,19 +677,19 @@ test "swap" { try a.setRatio(50, 23); try b.setRatio(17, 3); - try testing.expect((try a.p.to(u32)) == 50); - try testing.expect((try a.q.to(u32)) == 23); + try testing.expect((try a.p.toInt(u32)) == 50); + try testing.expect((try a.q.toInt(u32)) == 23); - try testing.expect((try b.p.to(u32)) == 17); - try testing.expect((try b.q.to(u32)) == 3); + try testing.expect((try b.p.toInt(u32)) == 17); + try testing.expect((try b.q.toInt(u32)) == 3); a.swap(&b); - try testing.expect((try a.p.to(u32)) == 17); - try testing.expect((try a.q.to(u32)) == 3); + try testing.expect((try a.p.toInt(u32)) == 17); + try testing.expect((try a.q.toInt(u32)) == 3); - try testing.expect((try b.p.to(u32)) == 50); - try testing.expect((try b.q.to(u32)) == 23); + try testing.expect((try b.p.toInt(u32)) == 50); + try testing.expect((try b.q.toInt(u32)) == 23); } test "order" { diff --git a/lib/std/std.zig b/lib/std/std.zig index cc61111746aa..5c997aebaf2e 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -93,6 +93,7 @@ pub const valgrind = @import("valgrind.zig"); pub const wasm = @import("wasm.zig"); pub const zig = @import("zig.zig"); pub const zip = @import("zip.zig"); +pub const zon = @import("zon.zig"); pub const start = @import("start.zig"); const root = @import("root"); diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index d6207d16a8c8..7b6554966a70 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -9446,7 +9446,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; @@ -11538,9 +11549,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( diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 7edb99ee03a1..354ba8b84d58 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -483,7 +483,7 @@ pub const Inst = struct { /// Uses the `pl_node` union field. `payload_index` points to a `FuncFancy`. func_fancy, /// Implements the `@import` builtin. - /// Uses the `str_tok` field. + /// Uses the `pl_tok` field. import, /// Integer literal that fits in a u64. Uses the `int` union field. int, @@ -1685,7 +1685,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, @@ -3832,6 +3832,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 }; diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 7f85f35f0512..e0da15865052 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -3,6 +3,8 @@ gpa: Allocator, tree: Ast, +options: Options, + nodes: std.MultiArrayList(Zoir.Node.Repr), extra: std.ArrayListUnmanaged(u32), limbs: std.ArrayListUnmanaged(std.math.big.Limb), @@ -12,12 +14,21 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d compile_errors: std.ArrayListUnmanaged(Zoir.CompileError), error_notes: std.ArrayListUnmanaged(Zoir.CompileError.Note), -pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zoir { +pub const Options = struct { + /// When false, string literals are not parsed. `string_literal` nodes will contain empty + /// strings, and errors that normally occur during string parsing will not be raised. + /// + /// `parseStrLit` and `strLitSizeHint` may be used to parse string literals after the fact. + parse_str_lits: bool = true, +}; + +pub fn generate(gpa: Allocator, tree: Ast, options: Options) Allocator.Error!Zoir { assert(tree.mode == .zon); var zg: ZonGen = .{ .gpa = gpa, .tree = tree, + .options = options, .nodes = .empty, .extra = .empty, .limbs = .empty, @@ -250,7 +261,20 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator .block_two_semicolon, .block, .block_semicolon, - => try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}), + => { + const size = switch (node_tags[node]) { + .block_two, .block_two_semicolon => @intFromBool(node_datas[node].lhs != 0) + @intFromBool(node_datas[node].rhs != 0), + .block, .block_semicolon => node_datas[node].rhs - node_datas[node].lhs, + else => unreachable, + }; + if (size == 0) { + try zg.addErrorNodeNotes(node, "void literals are not available in ZON", .{}, &.{ + try zg.errNoteNode(node, "void union payloads can be represented by enum literals", .{}), + }); + } else { + try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}); + } + }, .array_init_one, .array_init_one_comma, @@ -415,46 +439,6 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator } } -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 +448,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 +472,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 +609,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 +748,20 @@ fn setNode(zg: *ZonGen, dest: Zoir.Node.Index, repr: Zoir.Node.Repr) void { zg.nodes.set(@intFromEnum(dest), repr); } -fn lowerStrLitError(zg: *ZonGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, raw_string: []const u8, offset: u32) Allocator.Error!void { - return err.lower(raw_string, offset, ZonGen.addErrorTokOff, .{ zg, token }); +fn lowerStrLitError( + zg: *ZonGen, + err: std.zig.string_literal.Error, + token: Ast.TokenIndex, + raw_string: []const u8, + offset: u32, +) Allocator.Error!void { + return ZonGen.addErrorTokOff( + zg, + token, + @intCast(offset + err.offset()), + "{}", + .{err.fmt(raw_string)}, + ); } fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) Allocator.Error!void { diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 716a9b90f01c..2dff70d70b9e 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -39,40 +39,82 @@ pub const Error = union(enum) { /// `''`. Not returned for string literals. empty_char_literal, - /// Returns `func(first_args[0], ..., first_args[n], offset + bad_idx, format, args)`. - pub fn lower( + const FormatMessage = struct { err: Error, raw_string: []const u8, - offset: u32, - comptime func: anytype, - first_args: anytype, - ) @typeInfo(@TypeOf(func)).@"fn".return_type.? { - switch (err) { - inline else => |bad_index_or_void, tag| { - const bad_index: u32 = switch (@TypeOf(bad_index_or_void)) { - void => 0, - else => @intCast(bad_index_or_void), - }; - const fmt_str: []const u8, const args = switch (tag) { - .invalid_escape_character => .{ "invalid escape character: '{c}'", .{raw_string[bad_index]} }, - .expected_hex_digit => .{ "expected hex digit, found '{c}'", .{raw_string[bad_index]} }, - .empty_unicode_escape_sequence => .{ "empty unicode escape sequence", .{} }, - .expected_hex_digit_or_rbrace => .{ "expected hex digit or '}}', found '{c}'", .{raw_string[bad_index]} }, - .invalid_unicode_codepoint => .{ "unicode escape does not correspond to a valid unicode scalar value", .{} }, - .expected_lbrace => .{ "expected '{{', found '{c}'", .{raw_string[bad_index]} }, - .expected_rbrace => .{ "expected '}}', found '{c}'", .{raw_string[bad_index]} }, - .expected_single_quote => .{ "expected singel quote ('), found '{c}'", .{raw_string[bad_index]} }, - .invalid_character => .{ "invalid byte in string or character literal: '{c}'", .{raw_string[bad_index]} }, - .empty_char_literal => .{ "empty character literal", .{} }, - }; - return @call(.auto, func, first_args ++ .{ - offset + bad_index, - fmt_str, - args, - }); - }, + }; + + fn formatMessage( + self: FormatMessage, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = f; + _ = options; + switch (self.err) { + .invalid_escape_character => |bad_index| try writer.print( + "invalid escape character: '{c}'", + .{self.raw_string[bad_index]}, + ), + .expected_hex_digit => |bad_index| try writer.print( + "expected hex digit, found '{c}'", + .{self.raw_string[bad_index]}, + ), + .empty_unicode_escape_sequence => try writer.writeAll( + "empty unicode escape sequence", + ), + .expected_hex_digit_or_rbrace => |bad_index| try writer.print( + "expected hex digit or '}}', found '{c}'", + .{self.raw_string[bad_index]}, + ), + .invalid_unicode_codepoint => try writer.writeAll( + "unicode escape does not correspond to a valid unicode scalar value", + ), + .expected_lbrace => |bad_index| try writer.print( + "expected '{{', found '{c}'", + .{self.raw_string[bad_index]}, + ), + .expected_rbrace => |bad_index| try writer.print( + "expected '}}', found '{c}'", + .{self.raw_string[bad_index]}, + ), + .expected_single_quote => |bad_index| try writer.print( + "expected single quote ('), found '{c}'", + .{self.raw_string[bad_index]}, + ), + .invalid_character => |bad_index| try writer.print( + "invalid byte in string or character literal: '{c}'", + .{self.raw_string[bad_index]}, + ), + .empty_char_literal => try writer.writeAll( + "empty character literal", + ), } } + + pub fn fmt(self: @This(), raw_string: []const u8) std.fmt.Formatter(formatMessage) { + return .{ .data = .{ + .err = self, + .raw_string = raw_string, + } }; + } + + pub fn offset(err: Error) usize { + return switch (err) { + inline .invalid_escape_character, + .expected_hex_digit, + .empty_unicode_escape_sequence, + .expected_hex_digit_or_rbrace, + .invalid_unicode_codepoint, + .expected_lbrace, + .expected_rbrace, + .expected_single_quote, + .invalid_character, + => |n| n, + .empty_char_literal => 0, + }; + } }; /// Asserts the slice starts and ends with single-quotes. diff --git a/lib/std/zon.zig b/lib/std/zon.zig new file mode 100644 index 000000000000..3f73b1936dd1 --- /dev/null +++ b/lib/std/zon.zig @@ -0,0 +1,42 @@ +//! 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. + +pub const parse = @import("zon/parse.zig"); +pub const stringify = @import("zon/stringify.zig"); + +test { + _ = @import("zon/parse.zig"); + _ = @import("zon/stringify.zig"); +} diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig new file mode 100644 index 000000000000..8324ea83bb37 --- /dev/null +++ b/lib/std/zon/parse.zig @@ -0,0 +1,2832 @@ +//! 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 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, + + 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 => return null, + } + } + }; + + 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.?), + } }; + } + + pub fn getLocation(self: Note, status: *const Status) Ast.Location { + switch (self) { + .zoir => |note| return zoirErrorLocation( + status.ast.?, + note.token, + note.node_or_offset, + ), + } + } + }; + + 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 { + message: []const u8, + owned: bool, + token: Ast.TokenIndex, + offset: u32, + + fn deinit(self: @This(), gpa: Allocator) void { + if (self.owned) { + gpa.free(self.message); + } + } + }; + + 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 only transitively contain the following supported types: + /// * bools + /// * fixed sized numeric types + /// * enums + /// * slices + /// * arrays + /// * structures + /// * unions + /// * optionals + /// * null + comptime T: type, + gpa: Allocator, + source: [:0]const u8, + status: ?*Status, + comptime 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; + + var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); + defer if (status == null) zoir.deinit(gpa); + if (status) |s| s.zoir = zoir; + if (zoir.hasCompileErrors()) return error.ParseZon; + + 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( + comptime T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + comptime 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( + comptime T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + node: Zoir.Node.Index, + status: ?*Status, + comptime options: Options, +) error{ OutOfMemory, ParseZon }!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, + .status = status, + }; + + return parser.parseExpr(T, options, 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, .many, .c => if (comptime requiresAllocator(Value)) { + @compileError(@typeName(Value) ++ ": free cannot free non slice pointers"); + }, + .slice => for (value) |item| { + free(gpa, item); + }, + } + return gpa.free(value); + }, + .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)) { + @compileError(@typeName(Value) ++ ": free cannot free untagged unions"); + } + } else switch (value) { + inline else => |_, tag| { + free(gpa, @field(value, @tagName(tag))); + }, + }, + .optional => if (value) |some| { + free(gpa, some); + }, + .void => {}, + .null => {}, + else => @compileError(@typeName(Value) ++ ": free cannot free this type"), + } +} + +fn requiresAllocator(comptime T: type) bool { + _ = valid_types; + return switch (@typeInfo(T)) { + .pointer => true, + .array => |array| 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), + else => false, + }; +} + +const Parser = struct { + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + + fn parseExpr( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + _ = valid_types; + switch (@typeInfo(T)) { + .bool => return self.parseBool(node), + .int => return self.parseInt(T, node), + .float => return self.parseFloat(T, node), + .@"enum" => return self.parseEnumLiteral(T, node), + .pointer => return self.parsePointer(T, options, node), + .array => return self.parseArray(T, options, node), + .@"struct" => |@"struct"| if (@"struct".is_tuple) + return self.parseTuple(T, options, node) + else + return self.parseStruct(T, options, node), + .@"union" => return self.parseUnion(T, options, node), + .optional => return self.parseOptional(T, options, node), + + else => @compileError("type '" ++ @typeName(T) ++ "' is not available in ZON"), + } + } + + fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { + switch (node.get(self.zoir)) { + .true => return true, + .false => return false, + else => return self.failNode(node, "expected type 'bool'"), + } + } + + fn parseInt( + self: @This(), + comptime 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 self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), + } + } + + fn parseFloat( + self: @This(), + comptime 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 self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), + } + } + + fn parseEnumLiteral( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .enum_literal => |string| { + // 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 + return enum_tags.get(string.get(self.zoir)) orelse + self.failUnexpectedField(T, node, null); + }, + else => return self.failNode(node, "expected enum literal"), + } + } + + fn parsePointer( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .string_literal => return try self.parseString(T, node), + .array_literal => |nodes| return try self.parseSlice(T, options, nodes), + .empty_literal => return try self.parseSlice(T, options, .{ .start = node, .len = 0 }), + else => return self.failExpectedContainer(T, node), + } + } + + fn parseString( + self: *@This(), + comptime 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_ptr != 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_ptr != null and @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* != 0) or + pointer.alignment != 1) + { + return self.failExpectedContainer(T, node); + } + + if (pointer.sentinel_ptr != null) { + return try buf.toOwnedSliceSentinel(self.gpa, 0); + } else { + return try buf.toOwnedSlice(self.gpa); + } + } + + fn parseSlice( + self: *@This(), + comptime T: type, + comptime options: Options, + 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 => @compileError(@typeName(T) ++ ": non slice pointers not supported"), + } + + // Allocate the slice + const sentinel = if (pointer.sentinel_ptr) |s| @as(*const pointer.child, @ptrCast(s)).* else null; + const slice = try self.gpa.allocWithOptions( + pointer.child, + nodes.len, + pointer.alignment, + sentinel, + ); + errdefer self.gpa.free(slice); + + // Parse the elements and return the slice + for (0..nodes.len) |i| { + errdefer if (options.free_on_error) { + for (slice[0..i]) |item| { + free(self.gpa, item); + } + }; + slice[i] = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); + } + + return slice; + } + + fn parseArray( + self: *@This(), + comptime T: type, + comptime options: Options, + 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 self.failExpectedContainer(T, node), + }; + + const array_info = @typeInfo(T).array; + + // Check if the size matches + if (nodes.len > array_info.len) { + return self.failNodeFmt( + nodes.at(array_info.len), + "index {} outside of tuple length {}", + .{ array_info.len, array_info.len }, + ); + } else if (nodes.len < array_info.len) { + switch (nodes.len) { + inline 0...array_info.len => |n| { + return self.failNodeFmt(node, "missing tuple field with index {}", .{n}); + }, + else => unreachable, + } + } + + // Parse the elements and return the array + var result: T = undefined; + for (0..result.len) |i| { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + for (result[0..i]) |item| { + free(self.gpa, item); + } + }; + + result[i] = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); + } + return result; + } + + fn parseStruct( + self: *@This(), + comptime T: type, + comptime options: Options, + 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 self.failExpectedContainer(T, node), + }; + + const field_infos = @typeInfo(T).@"struct".fields; + + // 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 struct + var result: T = undefined; + var field_found: [field_infos.len]bool = .{false} ** field_infos.len; + + // If we fail partway through, free all already initialized fields + var initialized: usize = 0; + errdefer if (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 field_index = b: { + const name = fields.names[i].get(self.zoir); + break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { + continue; + } else { + return self.failUnexpectedField(T, node, i); + }; + }; + + // We now know the array is not zero sized (assert this so the code compiles) + if (field_found.len == 0) unreachable; + + if (field_found[field_index]) { + 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[i]; + const token = self.ast.firstToken(field_node) - 2; + return self.failTokenFmt(token, 0, "duplicate struct field name", .{}); + } + field_found[field_index] = true; + + switch (field_index) { + inline 0...(field_infos.len - 1) => |j| { + if (field_infos[j].is_comptime) { + return self.failRuntimeValueComptimeVar(node, j); + } else { + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + options, + 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(), + comptime T: type, + comptime options: Options, + 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 self.failExpectedContainer(T, node), + }; + + 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 (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.failRuntimeValueComptimeVar(node, i); + } else { + result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); + } + } + } + + return result; + } + + fn parseUnion( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + const @"union" = @typeInfo(T).@"union"; + const field_infos = @"union".fields; + + if (field_infos.len == 0) { + @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); + } + + // 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 => |string| { + // The union must be tagged for an enum literal to coerce to it + if (@"union".tag_type == null) { + return self.failNode(node, "expected union"); + } + + // 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: { + break :b field_indices.get(string.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, null); + }; + + // 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 self.failNode(node, "expected union"); + } + + // Fill in the field we found + const field_name = struct_fields.names[0]; + const field_val = struct_fields.vals.at(0); + const field_index = field_indices.get(field_name.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, 0); + + 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, options, field_val); + return @unionInit(T, field_infos[i].name, value); + } + }, + else => unreachable, // Can't be out of bounds + } + }, + else => return self.failNode(node, "expected union"), + } + } + + fn parseOptional( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + if (node.get(self.zoir) == .null) { + return null; + } + + return try self.parseExpr(@typeInfo(T).optional.child, options, node); + } + + fn failTokenFmt( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + if (self.status) |s| s.type_check = .{ + .token = token, + .offset = offset, + .message = try std.fmt.allocPrint(self.gpa, fmt, args), + .owned = true, + }; + 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, + }); + } + + fn failCannotRepresent( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); + } + + fn failUnexpectedField( + self: @This(), + T: type, + node: Zoir.Node.Index, + field: ?usize, + ) 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| { + if (info.fields.len == 0) { + return self.failTokenFmt(token, 0, "unexpected field, no fields expected", .{}); + } else { + const msg = "unexpected field, supported fields: "; + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, msg.len * 2); + 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("{}", .{std.zig.fmtId(field_info.name)}); + } + return self.failToken(.{ + .token = token, + .offset = 0, + .message = try buf.toOwnedSlice(self.gpa), + .owned = true, + }); + } + }, + else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + } + } + + fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + switch (@typeInfo(T)) { + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + return self.failNode(node, "expected tuple"); + } else { + return self.failNode(node, "expected struct"); + }, + .@"union" => return self.failNode(node, "expected union"), + .array => return self.failNode(node, "expected tuple"), + .pointer => |pointer| { + if (pointer.child == u8 and + pointer.size == .slice and + pointer.is_const and + (pointer.sentinel_ptr == null or @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* == 0) and + pointer.alignment == 1) + { + return self.failNode(node, "expected string"); + } else { + return self.failNode(node, "expected tuple"); + } + }, + else => {}, + } + @compileError("unreachable, should not be called for type " ++ @typeName(T)); + } + + // 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 failRuntimeValueComptimeVar( + 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.failTokenFmt(token, 0, "cannot store runtime value in compile time variable", .{}); + } +}; + +fn intFromFloatExact(comptime T: type, value: anytype) ?T { + switch (@typeInfo(@TypeOf(value))) { + .float => {}, + else => @compileError(@typeName(@TypeOf(value)) ++ " is not a runtime floating point type"), + } + switch (@typeInfo(T)) { + .int => {}, + else => @compileError(@typeName(T) ++ " is not a runtime integer type"), + } + + 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 @as(T, @intFromFloat(value)); +} + +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([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([]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)); +} + +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, supported fields: x, y\n", + "{}", + .{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, supported fields: x\n", + "{}", + .{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, supported fields: x, y\n", + "{}", + .{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}", &status, .{}), + ); + try std.testing.expectFmt("1:12: error: duplicate struct field name\n", "{}", .{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, no fields expected\n", "{}", .{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 store runtime value in compile time variable + \\ + , "{}", .{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 store runtime value in compile time variable + \\ + , "{}", .{status}); + } +} + +// Test sizes 0 to 3 since small sizes get parsed differently +test "std.zon arrays and slices" { + // Issue: https://github.com/ziglang/zig/issues/20881 + if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; + + 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 tuple 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 tuple 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: missing tuple field with index 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: missing tuple field with index 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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 tuple\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 field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", + "{}", + .{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 field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", + "{}", + .{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); + } +} diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig new file mode 100644 index 000000000000..e4d94f5f9757 --- /dev/null +++ b/lib/std/zon/stringify.zig @@ -0,0 +1,2088 @@ +//! ZON can be serialized with `serialize`. +//! +//! The following functions are provided for serializing recursive types: +//! * `serializeMaxDepth` +//! * `serializeArbitraryDepth` +//! +//! For additional control over serialization, see `Serializer`. +//! +//! Transitively Supported types: +//! * bools +//! * fixed sized numeric types +//! * exhaustive enums (non-exhaustive enums may have no literal representation) +//! * enum literals +//! * slices +//! * arrays +//! * structs +//! * tagged unions +//! * optionals +//! * null +//! +//! Unsupported types will fail to serialize at compile time. + +const std = @import("std"); + +/// Options for `serialize`. +pub const SerializeOptions = struct { + /// If false, all whitespace is emitted. Otherwise, whitespace is emitted in the standard Zig + /// style when possible. + whitespace: bool = true, + /// If true, unsigned integers with <= 21 bits are written as their corresponding UTF8 codepoint + /// instead of a numeric literal if one exists. + emit_utf8_codepoints: bool = false, + /// If true, slices of u8s, and pointers to arrays of u8s 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, + comptime options: SerializeOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.value(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .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. +pub fn serializeMaxDepth( + val: anytype, + comptime options: SerializeOptions, + writer: anytype, + depth: usize, +) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.valueMaxDepth(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .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, + comptime options: SerializeOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.valueArbitraryDepth(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }); +} + +const RecursiveTypeBuffer = [32]type; + +fn typeIsRecursive(comptime T: type) bool { + comptime var buf: RecursiveTypeBuffer = undefined; + return comptime typeIsRecursiveImpl(T, buf[0..0]); +} + +fn typeIsRecursiveImpl(comptime T: type, comptime visited_arg: []type) bool { + comptime var visited = visited_arg; + + // Check if we've already seen this type + inline for (visited) |found| { + if (T == found) { + return true; + } + } + + // Add this type to the stack + if (visited.len >= @typeInfo(RecursiveTypeBuffer).array.len) { + @compileError("recursion limit"); + } + visited.ptr[visited.len] = T; + visited.len += 1; + + // Recurse + switch (@typeInfo(T)) { + .pointer => |pointer| return typeIsRecursiveImpl(pointer.child, visited), + .array => |array| return typeIsRecursiveImpl(array.child, visited), + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) { + return true; + } + }, + .@"union" => |@"union"| inline for (@"union".fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) { + return true; + } + }, + .optional => |optional| return typeIsRecursiveImpl(optional.child, visited), + else => {}, + } + return false; +} + +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, +}; + +/// Options for serialization of an individual value. +pub const ValueOptions = struct { + emit_utf8_codepoints: bool = false, + 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/item. 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/items 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` +/// * `utf8Codepoint` +/// * `slice` +/// * `sliceMaxDepth` +/// * `sliceArbitraryDepth` +/// * `string` +/// * `multilineString` +/// +/// For manual serialization of containers, see: +/// * `startStruct` +/// * `startTuple` +/// * `startSlice` +/// +/// # 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 { + comptimeAssertNoRecursion(@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 { + switch (@typeInfo(@TypeOf(val))) { + .int => |int_info| if (options.emit_utf8_codepoints and + int_info.signedness == .unsigned and + int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .comptime_int => if (options.emit_utf8_codepoints and + val > 0 and + val <= std.math.maxInt(u21) and + std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) 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.writer.writeByte('.'); + try self.ident(@tagName(val)); + }, + .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + } else { + @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", + ); + }, + .void => try self.writer.writeAll("{}"), + .pointer => |pointer| { + const child_type = switch (@typeInfo(pointer.child)) { + .array => |array| array.child, + else => if (pointer.size != .slice) @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", + ) else pointer.child, + }; + if (child_type == u8 and !options.emit_strings_as_containers) { + try self.string(val); + } else { + try self.sliceImpl(val, options); + } + }, + .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 = if (options.emit_default_optional_fields) b: { + break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; + } else b: { + var fields = @"struct".fields.len; + var skipped = [1]bool{false} ** @"struct".fields.len; + inline for (@"struct".fields, &skipped) |field_info, *skip| { + if (field_info.default_value_ptr) |default_field_value_opaque| { + const field_value = @field(val, field_info.name); + const default_field_value: *const @TypeOf(field_value) = @ptrCast( + @alignCast(default_field_value_opaque), + ); + if (std.meta.eql(field_value, default_field_value.*)) { + 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"| if (@"union".tag_type == null) { + @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); + } else { + 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"); + }, + + else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), + } + } + + /// 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, .comptime_float => if (std.math.isNan(val)) { + return self.writer.writeAll("nan"); + } else if (@as(f128, val) == std.math.inf(f128)) { + return self.writer.writeAll("inf"); + } else if (@as(f128, val) == -std.math.inf(f128)) { + return self.writer.writeAll("-inf"); + } else { + try std.fmt.format(self.writer, "{d}", .{val}); + }, + else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), + } + } + + fn identNeedsEscape(name: []const u8) bool { + std.debug.assert(name.len != 0); + for (name, 0..) |c, i| { + switch (c) { + 'A'...'Z', 'a'...'z', '_' => {}, + '0'...'9' => if (i == 0) return true, + else => return true, + } + } + return std.zig.Token.keywords.has(name); + } + + /// Serialize `name` as an identifier. + /// + /// Escapes the identifier if necessary. + pub fn ident(self: *Self, name: []const u8) Writer.Error!void { + if (identNeedsEscape(name)) { + try self.writer.writeAll("@\""); + try self.writer.writeAll(name); + try self.writer.writeByte('"'); + } else { + try self.writer.writeAll(name); + } + } + + /// Serialize `val` as a UTF8 codepoint. + /// + /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. + pub fn utf8Codepoint( + 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 slice. + /// + /// Will fail at comptime if `val` is not an array or slice. + pub fn slice(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.sliceArbitraryDepth(val, options); + } + + /// Like `value`, but recursive types are allowed. + /// + /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. + pub fn sliceMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + try self.sliceArbitraryDepth(val, options); + } + + /// Like `value`, but recursive types are allowed. + /// + /// It is the caller's responsibility to ensure that `val` does not contain cycles. + pub fn sliceArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.sliceImpl(val, options); + } + + fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.itemArbitraryDepth(item_val, options); + } + try container.finish(); + } + + /// 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); + } + + /// Creates a `Slice` for writing ZON slices item by item. + pub fn startSlice( + self: *Self, + options: SerializeContainerOptions, + ) Writer.Error!Slice { + return Slice.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); + } + }; + + /// Writes ZON slices field by field. + pub const Slice = struct { + container: Container, + + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Slice { + try parent.writer.writeByte('&'); + return .{ + .container = try Container.start(parent, .anon, options), + }; + } + + /// Finishes serializing the slice. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Slice) Writer.Error!void { + try self.container.finish(); + self.* = undefined; + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. + pub fn item( + self: *Slice, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.field(null, val, options); + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. + pub fn itemMaxDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try self.container.fieldMaxDepth(null, val, options, depth); + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by + /// `valueArbitraryDepth`. + pub fn itemArbitraryDepth( + self: *Slice, + 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 item value yourself. + pub fn itemPrefix(self: *Slice) Writer.Error!void { + try self.container.fieldPrefix(null); + } + }; + + 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.writer.writeByte('.'); + 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 { + comptimeAssertNoRecursion(@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, + }; + } + }; + + fn comptimeAssertNoRecursion(comptime T: type) void { + if (comptime typeIsRecursive(T)) { + @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); + } + } + }; +} + +/// 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, + comptime 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(), .{}); + + // Minimal case + try sz.utf8Codepoint('a'); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); + + try sz.int('a'); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('a', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('a', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + // Short escaped codepoint + try sz.utf8Codepoint('\n'); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); + + try sz.int('\n'); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); + + // Large codepoint + try sz.utf8Codepoint('âš¡'); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); + + try sz.int('âš¡'); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + // Invalid codepoint + try std.testing.expectError(error.InvalidCodepoint, sz.utf8Codepoint(0x110000 + 1)); + + try sz.int(0x110000 + 1); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + // Valid codepoint, not a codepoint type + try sz.value(@as(u22, 'a'), .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(@as(i32, 'a'), .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + // Make sure value options are passed to children + try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = false }); + 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.slice("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_utf8_codepoints = true }, + ); + + // 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_utf8_codepoints = true, + }, + ); + + // 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_utf8_codepoints = true, + }, + ); + + 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.sliceMaxDepth(maybe_recurse, .{}, 2), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + try sz.sliceArbitraryDepth(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.sliceMaxDepth(maybe_recurse, .{}, 3); + try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + buf.clearRetainingCapacity(); + + try sz.sliceArbitraryDepth(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.sliceMaxDepth(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.startSlice(.{}); + try std.testing.expectError(error.ExceededMaxDepth, a.itemMaxDepth(1, .{}, 0)); + try a.itemMaxDepth(8, .{}, 1); + try a.item(9, .{}); + try a.itemArbitraryDepth(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, + \\ .d = {}, + \\ .e = null, + \\} + , + .{ + .a = true, + .b = false, + .c = .foo, + .d = {}, + .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 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", + }, .{}); +} diff --git a/src/Compilation.zig b/src/Compilation.zig index ac8fb8a59bce..d6699b8b59d7 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2241,7 +2241,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| { @@ -3232,10 +3235,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; + } } } for (zcu.failed_embed_files.values()) |error_msg| { @@ -3652,6 +3661,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, @@ -4291,6 +4309,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(); @@ -4344,7 +4363,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, }); diff --git a/src/InternPool.zig b/src/InternPool.zig index a92e93705cc9..c43d542988ce 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -4381,7 +4381,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; @@ -7875,7 +7875,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; }, @@ -7892,7 +7892,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; }, @@ -7909,7 +7909,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; }, @@ -7924,7 +7924,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)), @@ -7942,7 +7942,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, @@ -7963,14 +7963,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)), @@ -8000,7 +8000,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{ diff --git a/src/Sema.zig b/src/Sema.zig index 833b05413f33..6e34d62b3a47 100644 --- a/src/Sema.zig +++ b/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 zon = @import("Sema/LowerZon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -5802,7 +5803,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, @@ -13917,9 +13918,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 => { @@ -13936,12 +13938,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 zon.lower( + 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 { diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig new file mode 100644 index 000000000000..4a27127bdae1 --- /dev/null +++ b/src/Sema/LowerZon.zig @@ -0,0 +1,819 @@ +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 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, + +/// Lowers the given file as ZON. +pub fn lower( + sema: *Sema, + file: *File, + file_index: Zcu.File.Index, + res_ty: Type, + import_loc: LazySrcLoc, + block: *Sema.Block, +) CompileError!InternPool.Index { + _ = try file.getZoir(sema.pt.zcu); + const lower_zon: LowerZon = .{ + .sema = sema, + .file = file, + .file_index = file_index, + .import_loc = import_loc, + .block = block, + }; + + return lower_zon.lowerExpr(.root, res_ty); +} + +fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { + return .{ + .base_node_inst = try self.sema.pt.zcu.intern_pool.trackZir( + self.sema.pt.zcu.gpa, + .main, + .{ .file = self.file_index, .inst = .main_struct_inst }, + ), + .offset = loc, + }; +} + +fn fail( + self: LowerZon, + loc: LazySrcLoc.Offset, + comptime format: []const u8, + args: anytype, +) error{ AnalysisFail, OutOfMemory } { + @branchHint(.cold); + const src_loc = try self.lazySrcLoc(loc); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, 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 { + switch (res_ty.zigTypeTag(self.sema.pt.zcu)) { + .bool => return self.lowerBool(node), + .int, .comptime_int => return self.lowerInt(node, res_ty), + .float, .comptime_float => return self.lowerFloat(node, res_ty), + .optional => return self.lowerOptional(node, res_ty), + .null => return self.lowerNull(node), + .@"enum" => return self.lowerEnum(node, res_ty), + .enum_literal => return self.lowerEnumLiteral(node, res_ty), + .array => return self.lowerArray(node, res_ty), + .@"struct" => return self.lowerStructOrTuple(node, res_ty), + .@"union" => return self.lowerUnion(node, res_ty), + .pointer => return self.lowerPointer(node, res_ty), + + .type, + .noreturn, + .undefined, + .error_union, + .error_set, + .@"fn", + .@"opaque", + .frame, + .@"anyframe", + .vector, + .void, + => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' not available in ZON", + .{res_ty.fmt(self.sema.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 => self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type 'bool'", + .{}, + ), + }; +} + +fn lowerInt( + self: LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + const gpa = self.sema.gpa; + 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_abs = node.getAstNode(self.file.zoir.?) }, + "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_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, + ); + } + } + } + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .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_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), val }, + ); + } + } + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .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_abs = node.getAstNode(self.file.zoir.?) }, + "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_abs = node.getAstNode(self.file.zoir.?) }, + "float value '{}' cannot be stored in integer type '{}'", + .{ val, res_ty.fmt(self.sema.pt) }, + ); + } + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = rational.p.toConst() }, + }, + }); + }, + .char_literal => |val| { + const rhs: u32 = 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 has less than 64 bits, we bounds check. We check at 64 instead of 32 in + // case LHS is signed. + if (std.math.cast(u6, lhs_info.bits)) |bits| { + const max_int: i64 = if (bits == 0) b: { + break :b 0; + } else b: { + break :b (@as(i64, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + }; + if (rhs > max_int) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, + ); + } + } + } + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = rhs }, + }, + }); + }, + + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; +} + +fn lowerFloat( + self: LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + switch (node.get(self.file.zoir.?)) { + .int_literal => |int| switch (int) { + .small => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatFromInt(val) }, + .f32_type => .{ .f32 = @floatFromInt(val) }, + .f64_type => .{ .f64 = @floatFromInt(val) }, + .f80_type => .{ .f80 = @floatFromInt(val) }, + .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(val) }, + else => unreachable, + }, + } }), + .big => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = val.toFloat(f16) }, + .f32_type => .{ .f32 = val.toFloat(f32) }, + .f64_type => .{ .f64 = val.toFloat(f64) }, + .f80_type => .{ .f80 = val.toFloat(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = val.toFloat(f128) }, + else => unreachable, + }, + } }), + }, + .float_literal => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatCast(val) }, + .f32_type => .{ .f32 = @floatCast(val) }, + .f64_type => .{ .f64 = @floatCast(val) }, + .f80_type => .{ .f80 = @floatCast(val) }, + .f128_type, .comptime_float_type => .{ .f128 = val }, + else => unreachable, + }, + } }), + .pos_inf => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = std.math.inf(f16) }, + .f32_type => .{ .f32 = std.math.inf(f32) }, + .f64_type => .{ .f64 = std.math.inf(f64) }, + .f80_type => .{ .f80 = std.math.inf(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = std.math.inf(f128) }, + else => unreachable, + }, + } }), + .neg_inf => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = -std.math.inf(f16) }, + .f32_type => .{ .f32 = -std.math.inf(f32) }, + .f64_type => .{ .f64 = -std.math.inf(f64) }, + .f80_type => .{ .f80 = -std.math.inf(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = -std.math.inf(f128) }, + else => unreachable, + }, + } }), + .nan => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = std.math.nan(f16) }, + .f32_type => .{ .f32 = std.math.nan(f32) }, + .f64_type => .{ .f64 = std.math.nan(f64) }, + .f80_type => .{ .f80 = std.math.nan(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = std.math.nan(f128) }, + else => unreachable, + }, + } }), + .char_literal => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatFromInt(val) }, + .f32_type => .{ .f32 = @floatFromInt(val) }, + .f64_type => .{ .f64 = @floatFromInt(val) }, + .f80_type => .{ .f80 = @floatFromInt(val) }, + .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(val) }, + else => unreachable, + }, + } }), + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + } +} + +fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + return switch (node.get(self.file.zoir.?)) { + .null => .null_value, + else => try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), + }; +} + +fn lowerNull(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { + switch (node.get(self.file.zoir.?)) { + .null => return .null_value, + else => return self.fail(.{ .node_abs = node.getAstNode(self.file.zoir.?) }, "expected null", .{}), + } +} + +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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + + if (nodes.len != array_info.len) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + } + + 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_abs = node.getAstNode(self.file.zoir.?) }, + "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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + } +} + +fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + 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 ip.get(gpa, self.sema.pt.tid, .{ .enum_literal = field_name_interned }); + }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + } +} + +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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + + const field_defaults = tuple_info.values.get(ip); + const field_types = tuple_info.types.get(ip); + const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len); + @memset(elems, .none); + + for (0..elem_nodes.len) |i| { + if (i >= elems.len) { + const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); + return self.fail( + .{ .node_abs = elem_node }, + "index {} outside tuple of length {}", + .{ + elems.len, + elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?), + }, + ); + } + elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); + + if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { + const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); + return self.fail( + .{ .node_abs = elem_node }, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + } + + for (0..elems.len) |i| { + if (elems[i] == .none and i < field_defaults.len) { + elems[i] = field_defaults[i]; + } + } + + for (elems, 0..) |val, i| { + if (val == .none) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + + const field_defaults = struct_info.field_inits.get(ip); + const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); + @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( + .{ .node_abs = field_node.getAstNode(self.file.zoir.?) }, + "unexpected field '{}'", + .{field_name.fmt(ip)}, + ); + }; + + const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]); + if (field_values[name_index] != .none) { + const field_node_ast = field_node.getAstNode(self.file.zoir.?); + const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; + return self.fail( + .{ .token_abs = field_name_token }, + "duplicate field '{}'", + .{field_name.fmt(ip)}, + ); + } + + 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)) { + const field_node_ast = field_node.getAstNode(self.file.zoir.?); + const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; + return self.fail( + .{ .token_abs = field_name_token }, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + } + } + + for (0..field_values.len) |i| { + if (field_values[i] == .none and i < field_defaults.len) { + field_values[i] = field_defaults[i]; + } + } + + const field_names = struct_info.field_names.get(ip); + for (field_values, field_names) |*value, name| { + if (value.* == .none) return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "missing field '{}'", + .{name.fmt(ip)}, + ); + } + + return self.sema.pt.intern(.{ .aggregate = .{ .ty = res_ty.toIntern(), .storage = .{ + .elems = field_values, + } } }); +} + +fn lowerPointer(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); + + if (ptr_info.flags.size != .slice) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "non slice pointers are not available in ZON", + .{}, + ); + } + + // 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, + try self.lazySrcLoc(.{ .node_abs = node.getAstNode(self.file.zoir.?) }), + )).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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + + const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); + + for (0..elem_nodes.len) |i| { + elems[i] = 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 ip.get(gpa, self.sema.pt.tid, .{ .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 ip.get(gpa, self.sema.pt.tid, .{ + .ptr = .{ + .ty = many_item_ptr_type, + .base_addr = .{ + .uav = .{ + .orig_ty = res_ty.toIntern(), + .val = array, + }, + }, + .byte_offset = 0, + }, + }); + + const len = (try self.sema.pt.intValue(Type.usize, elems.len)).toIntern(); + + return ip.get(gpa, self.sema.pt.tid, .{ .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_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + if (fields.names.len != 1) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + } + 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 self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + + const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + }; + const tag_int = if (enum_tag_info.values.len == 0) b: { + // Auto numbered fields + break :b try self.sema.pt.intern(.{ .int = .{ + .ty = enum_tag_info.tag_ty, + .storage = .{ .u64 = name_index }, + } }); + } else b: { + // Explicitly numbered fields + break :b enum_tag_info.values.get(ip)[name_index]; + }; + const tag = try self.sema.pt.intern(.{ .enum_tag = .{ + .ty = union_info.enum_tag_ty, + .int = tag_int, + } }); + 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( + .{ .node_abs = field_node.getAstNode(self.file.zoir.?) }, + "expected type 'void'", + .{}, + ); + } + break :b try self.lowerExpr(field_node, field_type); + } else b: { + if (field_type.toIntern() != .void_type) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + } + break :b .void_value; + }; + return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ + .ty = res_ty.toIntern(), + .tag = tag, + .val = val, + }); +} diff --git a/src/Value.zig b/src/Value.zig index c8ae997d9823..980d2c31908e 100644 --- a/src/Value.zig +++ b/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), diff --git a/src/Zcu.zig b/src/Zcu.zig index c75cd5d40cb6..60cff147040a 100644 --- a/src/Zcu.zig +++ b/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); @@ -669,6 +671,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. @@ -701,7 +705,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); @@ -775,11 +791,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; @@ -892,6 +921,7 @@ pub const File = struct { pub const Index = InternPool.FileIndex; }; +/// Represents the contents of a file loaded with `@embedFile`. pub const EmbedFile = struct { /// Relative to the owning module's root directory. sub_file_path: InternPool.NullTerminatedString, @@ -2359,6 +2389,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; @@ -3443,8 +3479,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; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index c6a16ee00cac..f010da1f68b3 100644 --- a/src/Zcu/PerThread.zig +++ b/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; diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index b0869f3a8f35..f79b5700a8f8 100644 --- a/src/codegen/llvm/Builder.zig +++ b/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; diff --git a/src/fmt.zig b/src/fmt.zig index 9ab581cad471..c86fb8533205 100644 --- a/src/fmt.zig +++ b/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()) { diff --git a/src/main.zig b/src/main.zig index 7d035ab135b6..56a14510b74a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6252,7 +6252,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()) { diff --git a/src/print_zir.zig b/src/print_zir.zig index afa94f40ed3a..d60b82868462 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -490,7 +490,6 @@ const Writer = struct { .enum_literal, .decl_ref, .decl_val, - .import, .ret_err_value, .ret_err_value_code, .param_anytype, @@ -517,6 +516,8 @@ const Writer = struct { .declaration => try self.writeDeclaration(stream, inst), .extended => try self.writeExtended(stream, inst), + + .import => try self.writeImport(stream, inst), } } @@ -2844,4 +2845,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); + } }; diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig new file mode 100644 index 000000000000..123d2b180f24 --- /dev/null +++ b/test/behavior/zon.zig @@ -0,0 +1,409 @@ +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" { + // comptime float + { + const actual: struct { comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[1])))); + try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[2])))); + } + + // 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])); + } +} diff --git a/test/behavior/zon/a.zon b/test/behavior/zon/a.zon new file mode 100644 index 000000000000..67fe32dafeb1 --- /dev/null +++ b/test/behavior/zon/a.zon @@ -0,0 +1 @@ +'a' diff --git a/test/behavior/zon/abc-escaped.zon b/test/behavior/zon/abc-escaped.zon new file mode 100644 index 000000000000..8672bb944874 --- /dev/null +++ b/test/behavior/zon/abc-escaped.zon @@ -0,0 +1 @@ +"ab\\c" diff --git a/test/behavior/zon/abc.zon b/test/behavior/zon/abc.zon new file mode 100644 index 000000000000..d1cc1b4e5215 --- /dev/null +++ b/test/behavior/zon/abc.zon @@ -0,0 +1 @@ +"abc" diff --git a/test/behavior/zon/array.zon b/test/behavior/zon/array.zon new file mode 100644 index 000000000000..8ee5ebe0f5f5 --- /dev/null +++ b/test/behavior/zon/array.zon @@ -0,0 +1 @@ +.{ 'a', 'b', 'c', 'd' } diff --git a/test/behavior/zon/enum_field.zon b/test/behavior/zon/enum_field.zon new file mode 100644 index 000000000000..33011e2f6589 --- /dev/null +++ b/test/behavior/zon/enum_field.zon @@ -0,0 +1 @@ +.{ .x = .z } diff --git a/test/behavior/zon/escaped_enum.zon b/test/behavior/zon/escaped_enum.zon new file mode 100644 index 000000000000..14e46d587c42 --- /dev/null +++ b/test/behavior/zon/escaped_enum.zon @@ -0,0 +1 @@ +.@"0\na" diff --git a/test/behavior/zon/escaped_struct.zon b/test/behavior/zon/escaped_struct.zon new file mode 100644 index 000000000000..f304aa632752 --- /dev/null +++ b/test/behavior/zon/escaped_struct.zon @@ -0,0 +1,2 @@ + +.{ .@"0" = 1.5, .@"foo" = 2 } diff --git a/test/behavior/zon/false.zon b/test/behavior/zon/false.zon new file mode 100644 index 000000000000..0064d7bc7d22 --- /dev/null +++ b/test/behavior/zon/false.zon @@ -0,0 +1,4 @@ +// Comment +false // Another comment +// Yet another comment + diff --git a/test/behavior/zon/floats.zon b/test/behavior/zon/floats.zon new file mode 100644 index 000000000000..052e34898923 --- /dev/null +++ b/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, +} diff --git a/test/behavior/zon/foo.zon b/test/behavior/zon/foo.zon new file mode 100644 index 000000000000..1e8ea91de539 --- /dev/null +++ b/test/behavior/zon/foo.zon @@ -0,0 +1 @@ +.foo diff --git a/test/behavior/zon/inf_and_nan.zon b/test/behavior/zon/inf_and_nan.zon new file mode 100644 index 000000000000..dec18d858a05 --- /dev/null +++ b/test/behavior/zon/inf_and_nan.zon @@ -0,0 +1,5 @@ +.{ + nan, + inf, + -inf, +} diff --git a/test/behavior/zon/ints.zon b/test/behavior/zon/ints.zon new file mode 100644 index 000000000000..fb1060324e2b --- /dev/null +++ b/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, +} diff --git a/test/behavior/zon/multiline_string.zon b/test/behavior/zon/multiline_string.zon new file mode 100644 index 000000000000..5908802ecc65 --- /dev/null +++ b/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 diff --git a/test/behavior/zon/none.zon b/test/behavior/zon/none.zon new file mode 100644 index 000000000000..19765bd501b6 --- /dev/null +++ b/test/behavior/zon/none.zon @@ -0,0 +1 @@ +null diff --git a/test/behavior/zon/slice-abc.zon b/test/behavior/zon/slice-abc.zon new file mode 100644 index 000000000000..e033b2e6ff87 --- /dev/null +++ b/test/behavior/zon/slice-abc.zon @@ -0,0 +1 @@ +.{'a', 'b', 'c'} \ No newline at end of file diff --git a/test/behavior/zon/slice-empty.zon b/test/behavior/zon/slice-empty.zon new file mode 100644 index 000000000000..c1ab9cdd5018 --- /dev/null +++ b/test/behavior/zon/slice-empty.zon @@ -0,0 +1 @@ +.{} \ No newline at end of file diff --git a/test/behavior/zon/slice1_no_newline.zon b/test/behavior/zon/slice1_no_newline.zon new file mode 100644 index 000000000000..7714116d4567 --- /dev/null +++ b/test/behavior/zon/slice1_no_newline.zon @@ -0,0 +1 @@ +.{ 1 } \ No newline at end of file diff --git a/test/behavior/zon/some.zon b/test/behavior/zon/some.zon new file mode 100644 index 000000000000..f599e28b8ab0 --- /dev/null +++ b/test/behavior/zon/some.zon @@ -0,0 +1 @@ +10 diff --git a/test/behavior/zon/string_embedded_null.zon b/test/behavior/zon/string_embedded_null.zon new file mode 100644 index 000000000000..420316636402 --- /dev/null +++ b/test/behavior/zon/string_embedded_null.zon @@ -0,0 +1 @@ +"a\nb\x00c" diff --git a/test/behavior/zon/true.zon b/test/behavior/zon/true.zon new file mode 100644 index 000000000000..27ba77ddaf61 --- /dev/null +++ b/test/behavior/zon/true.zon @@ -0,0 +1 @@ +true diff --git a/test/behavior/zon/tuple.zon b/test/behavior/zon/tuple.zon new file mode 100644 index 000000000000..61e6be9fcf9d --- /dev/null +++ b/test/behavior/zon/tuple.zon @@ -0,0 +1 @@ +.{ 1.2, true, "hello", 3 } diff --git a/test/behavior/zon/union1.zon b/test/behavior/zon/union1.zon new file mode 100644 index 000000000000..3dc052f89239 --- /dev/null +++ b/test/behavior/zon/union1.zon @@ -0,0 +1 @@ +.{ .x = 1.5 } diff --git a/test/behavior/zon/union2.zon b/test/behavior/zon/union2.zon new file mode 100644 index 000000000000..5c25d1569001 --- /dev/null +++ b/test/behavior/zon/union2.zon @@ -0,0 +1 @@ +.{ .y = true } diff --git a/test/behavior/zon/union3.zon b/test/behavior/zon/union3.zon new file mode 100644 index 000000000000..3baf4ac17349 --- /dev/null +++ b/test/behavior/zon/union3.zon @@ -0,0 +1 @@ +.z diff --git a/test/behavior/zon/vec0.zon b/test/behavior/zon/vec0.zon new file mode 100644 index 000000000000..47c47bc057a0 --- /dev/null +++ b/test/behavior/zon/vec0.zon @@ -0,0 +1 @@ +.{} diff --git a/test/behavior/zon/vec1.zon b/test/behavior/zon/vec1.zon new file mode 100644 index 000000000000..3dc052f89239 --- /dev/null +++ b/test/behavior/zon/vec1.zon @@ -0,0 +1 @@ +.{ .x = 1.5 } diff --git a/test/behavior/zon/vec2.zon b/test/behavior/zon/vec2.zon new file mode 100644 index 000000000000..cc4bff59b9a9 --- /dev/null +++ b/test/behavior/zon/vec2.zon @@ -0,0 +1 @@ +.{ .x = 1.5, .y = 2 } diff --git a/test/behavior/zon/z.zon b/test/behavior/zon/z.zon new file mode 100644 index 000000000000..6ad22b40ef89 --- /dev/null +++ b/test/behavior/zon/z.zon @@ -0,0 +1 @@ +'z' diff --git a/test/cases/compile_errors/@import_zon_addr_slice.zig b/test/cases/compile_errors/@import_zon_addr_slice.zig new file mode 100644 index 000000000000..df7235d31acc --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_array_len.zig b/test/cases/compile_errors/@import_zon_array_len.zig new file mode 100644 index 000000000000..dfe5cbb0b985 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_bad_import.zig b/test/cases/compile_errors/@import_zon_bad_import.zig new file mode 100644 index 000000000000..4df03ce2dbfb --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig new file mode 100644 index 000000000000..9cbae754045a --- /dev/null +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: *struct { u8, u8, u8 } = @import("zon/array.zon"); + _ = f; +} + +// error +// imports=zon/array.zon +// +// array.zon:1:2: error: non slice pointers are not available in ZON +// tmp.zig:2:47: note: imported here diff --git a/test/cases/compile_errors/@import_zon_doc_comment.zig b/test/cases/compile_errors/@import_zon_doc_comment.zig new file mode 100644 index 000000000000..126f292652b5 --- /dev/null +++ b/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' diff --git a/test/cases/compile_errors/@import_zon_double_negation_float.zig b/test/cases/compile_errors/@import_zon_double_negation_float.zig new file mode 100644 index 000000000000..e3263dd7c611 --- /dev/null +++ b/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 '-' diff --git a/test/cases/compile_errors/@import_zon_double_negation_int.zig b/test/cases/compile_errors/@import_zon_double_negation_int.zig new file mode 100644 index 000000000000..9c3b3bd96e15 --- /dev/null +++ b/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 '-' diff --git a/test/cases/compile_errors/@import_zon_enum_embedded_null.zig b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig new file mode 100644 index 000000000000..a56182e87f4e --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_expected_void.zig b/test/cases/compile_errors/@import_zon_expected_void.zig new file mode 100644 index 000000000000..82f446a914c0 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_invalid_character.zig b/test/cases/compile_errors/@import_zon_invalid_character.zig new file mode 100644 index 000000000000..c169db8a37b2 --- /dev/null +++ b/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' diff --git a/test/cases/compile_errors/@import_zon_invalid_number.zig b/test/cases/compile_errors/@import_zon_invalid_number.zig new file mode 100644 index 000000000000..740628b3eb79 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_invalid_string.zig b/test/cases/compile_errors/@import_zon_invalid_string.zig new file mode 100644 index 000000000000..0ae367e4ae81 --- /dev/null +++ b/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' diff --git a/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig b/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig new file mode 100644 index 000000000000..5e5b1e1a1edf --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_neg_char.zig b/test/cases/compile_errors/@import_zon_neg_char.zig new file mode 100644 index 000000000000..e5239c14bdeb --- /dev/null +++ b/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 '-' diff --git a/test/cases/compile_errors/@import_zon_neg_nan.zig b/test/cases/compile_errors/@import_zon_neg_nan.zig new file mode 100644 index 000000000000..acc88a80c8fa --- /dev/null +++ b/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 '-' diff --git a/test/cases/compile_errors/@import_zon_negative_zero.zig b/test/cases/compile_errors/@import_zon_negative_zero.zig new file mode 100644 index 000000000000..69918c926e15 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_no_rt.zig b/test/cases/compile_errors/@import_zon_no_rt.zig new file mode 100644 index 000000000000..b7518b0cb1f6 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_number_fail_limits.zig b/test/cases/compile_errors/@import_zon_number_fail_limits.zig new file mode 100644 index 000000000000..ee6cc2c2d6ab --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_char_0.zig b/test/cases/compile_errors/@import_zon_oob_char_0.zig new file mode 100644 index 000000000000..5c318cc1b875 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_char_1.zig b/test/cases/compile_errors/@import_zon_oob_char_1.zig new file mode 100644 index 000000000000..1a6fffac8642 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_int_0.zig b/test/cases/compile_errors/@import_zon_oob_int_0.zig new file mode 100644 index 000000000000..a830710d9111 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_int_1.zig b/test/cases/compile_errors/@import_zon_oob_int_1.zig new file mode 100644 index 000000000000..c79e586eccac --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_int_2.zig b/test/cases/compile_errors/@import_zon_oob_int_2.zig new file mode 100644 index 000000000000..a78c533dcd81 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_oob_int_3.zig b/test/cases/compile_errors/@import_zon_oob_int_3.zig new file mode 100644 index 000000000000..ee2015677ee1 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_struct_dup_field.zig b/test/cases/compile_errors/@import_zon_struct_dup_field.zig new file mode 100644 index 000000000000..ff9cb12f82ff --- /dev/null +++ b/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:3:6: error: duplicate field 'name' +// tmp.zig:3:44: note: imported here diff --git a/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig b/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig new file mode 100644 index 000000000000..ef818499d6e4 --- /dev/null +++ b/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:15: error: value stored in comptime field does not match the default value of the field +// tmp.zig:6:29: note: imported here diff --git a/test/cases/compile_errors/@import_zon_syntax_error.zig b/test/cases/compile_errors/@import_zon_syntax_error.zig new file mode 100644 index 000000000000..c4719c346188 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig new file mode 100644 index 000000000000..f1cf5896f94b --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_type_decl.zig b/test/cases/compile_errors/@import_zon_type_decl.zig new file mode 100644 index 000000000000..add73f22e64c --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_type_expr_array.zig b/test/cases/compile_errors/@import_zon_type_expr_array.zig new file mode 100644 index 000000000000..2272738328c0 --- /dev/null +++ b/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 '.' diff --git a/test/cases/compile_errors/@import_zon_type_expr_fn.zig b/test/cases/compile_errors/@import_zon_type_expr_fn.zig new file mode 100644 index 000000000000..d2e205e29600 --- /dev/null +++ b/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 '.' diff --git a/test/cases/compile_errors/@import_zon_type_expr_struct.zig b/test/cases/compile_errors/@import_zon_type_expr_struct.zig new file mode 100644 index 000000000000..e764d0ac19ec --- /dev/null +++ b/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 '.' diff --git a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig new file mode 100644 index 000000000000..63466e1c8ecf --- /dev/null +++ b/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 '.' diff --git a/test/cases/compile_errors/@import_zon_type_mismatch.zig b/test/cases/compile_errors/@import_zon_type_mismatch.zig new file mode 100644 index 000000000000..38a50ee7d98d --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_unescaped_newline.zig b/test/cases/compile_errors/@import_zon_unescaped_newline.zig new file mode 100644 index 000000000000..160095c0c24a --- /dev/null +++ b/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' diff --git a/test/cases/compile_errors/@import_zon_unknown_ident.zig b/test/cases/compile_errors/@import_zon_unknown_ident.zig new file mode 100644 index 000000000000..cf3ff652b553 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/@import_zon_void.zig b/test/cases/compile_errors/@import_zon_void.zig new file mode 100644 index 000000000000..3f4f2e5481e3 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/zon/addr_slice.zon b/test/cases/compile_errors/zon/addr_slice.zon new file mode 100644 index 000000000000..a0bfcba08fee --- /dev/null +++ b/test/cases/compile_errors/zon/addr_slice.zon @@ -0,0 +1,3 @@ +.{ + .value = &.{ 1, 2, 3 }, +} diff --git a/test/cases/compile_errors/zon/array.zon b/test/cases/compile_errors/zon/array.zon new file mode 100644 index 000000000000..8c639ac4cd2a --- /dev/null +++ b/test/cases/compile_errors/zon/array.zon @@ -0,0 +1 @@ +.{ 'a', 'b', 'c' } diff --git a/test/cases/compile_errors/zon/char_32.zon b/test/cases/compile_errors/zon/char_32.zon new file mode 100644 index 000000000000..1585185a1881 --- /dev/null +++ b/test/cases/compile_errors/zon/char_32.zon @@ -0,0 +1 @@ +' ' \ No newline at end of file diff --git a/test/cases/compile_errors/zon/desktop.ini b/test/cases/compile_errors/zon/desktop.ini new file mode 100644 index 000000000000..0a364e9f2edf --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/zon/doc_comment.zon b/test/cases/compile_errors/zon/doc_comment.zon new file mode 100644 index 000000000000..649d8d5df3f7 --- /dev/null +++ b/test/cases/compile_errors/zon/doc_comment.zon @@ -0,0 +1,2 @@ +//! Doc comments aren't allowed in ZON +.{} diff --git a/test/cases/compile_errors/zon/double_negation_float.zon b/test/cases/compile_errors/zon/double_negation_float.zon new file mode 100644 index 000000000000..646b67f8f817 --- /dev/null +++ b/test/cases/compile_errors/zon/double_negation_float.zon @@ -0,0 +1 @@ +--1.0 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/double_negation_int.zon b/test/cases/compile_errors/zon/double_negation_int.zon new file mode 100644 index 000000000000..bfbbe2bf6abb --- /dev/null +++ b/test/cases/compile_errors/zon/double_negation_int.zon @@ -0,0 +1 @@ +--1 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/enum_embedded_null.zon b/test/cases/compile_errors/zon/enum_embedded_null.zon new file mode 100644 index 000000000000..9e5b888e1205 --- /dev/null +++ b/test/cases/compile_errors/zon/enum_embedded_null.zon @@ -0,0 +1,4 @@ +.{ + .@"\x00", + 10, +} diff --git a/test/cases/compile_errors/zon/int_32.zon b/test/cases/compile_errors/zon/int_32.zon new file mode 100644 index 000000000000..1758dddccea2 --- /dev/null +++ b/test/cases/compile_errors/zon/int_32.zon @@ -0,0 +1 @@ +32 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/int_neg_33.zon b/test/cases/compile_errors/zon/int_neg_33.zon new file mode 100644 index 000000000000..2242a7b3c061 --- /dev/null +++ b/test/cases/compile_errors/zon/int_neg_33.zon @@ -0,0 +1 @@ +-33 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/invalid_character.zon b/test/cases/compile_errors/zon/invalid_character.zon new file mode 100644 index 000000000000..7d2e60640845 --- /dev/null +++ b/test/cases/compile_errors/zon/invalid_character.zon @@ -0,0 +1 @@ +'\a' diff --git a/test/cases/compile_errors/zon/invalid_number.zon b/test/cases/compile_errors/zon/invalid_number.zon new file mode 100644 index 000000000000..0c96bf6190c3 --- /dev/null +++ b/test/cases/compile_errors/zon/invalid_number.zon @@ -0,0 +1 @@ +368934881474191032a32 diff --git a/test/cases/compile_errors/zon/invalid_string.zon b/test/cases/compile_errors/zon/invalid_string.zon new file mode 100644 index 000000000000..aed60487ca94 --- /dev/null +++ b/test/cases/compile_errors/zon/invalid_string.zon @@ -0,0 +1 @@ +"\"\a\"" diff --git a/test/cases/compile_errors/zon/large_number.zon b/test/cases/compile_errors/zon/large_number.zon new file mode 100644 index 000000000000..1ce484120aa1 --- /dev/null +++ b/test/cases/compile_errors/zon/large_number.zon @@ -0,0 +1 @@ +36893488147419103232 diff --git a/test/cases/compile_errors/zon/leading_zero_in_integer.zon b/test/cases/compile_errors/zon/leading_zero_in_integer.zon new file mode 100644 index 000000000000..58aba07363df --- /dev/null +++ b/test/cases/compile_errors/zon/leading_zero_in_integer.zon @@ -0,0 +1 @@ +0012 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/neg_char.zon b/test/cases/compile_errors/zon/neg_char.zon new file mode 100644 index 000000000000..b14b16f3d6e9 --- /dev/null +++ b/test/cases/compile_errors/zon/neg_char.zon @@ -0,0 +1 @@ +-'a' diff --git a/test/cases/compile_errors/zon/neg_nan.zon b/test/cases/compile_errors/zon/neg_nan.zon new file mode 100644 index 000000000000..c5c60a60cd2e --- /dev/null +++ b/test/cases/compile_errors/zon/neg_nan.zon @@ -0,0 +1 @@ +-nan diff --git a/test/cases/compile_errors/zon/negative_zero.zon b/test/cases/compile_errors/zon/negative_zero.zon new file mode 100644 index 000000000000..16593f0b75b7 --- /dev/null +++ b/test/cases/compile_errors/zon/negative_zero.zon @@ -0,0 +1 @@ +-0 \ No newline at end of file diff --git a/test/cases/compile_errors/zon/simple_union.zon b/test/cases/compile_errors/zon/simple_union.zon new file mode 100644 index 000000000000..0ac6526f2c64 --- /dev/null +++ b/test/cases/compile_errors/zon/simple_union.zon @@ -0,0 +1 @@ +.{ .a = 10 } diff --git a/test/cases/compile_errors/zon/struct.zon b/test/cases/compile_errors/zon/struct.zon new file mode 100644 index 000000000000..85ce0281afd5 --- /dev/null +++ b/test/cases/compile_errors/zon/struct.zon @@ -0,0 +1,4 @@ +.{ + .boolean = true, + .number = 123, +} diff --git a/test/cases/compile_errors/zon/struct_dup_field.zon b/test/cases/compile_errors/zon/struct_dup_field.zon new file mode 100644 index 000000000000..9363e2f53e14 --- /dev/null +++ b/test/cases/compile_errors/zon/struct_dup_field.zon @@ -0,0 +1,4 @@ +.{ + .name = 10, + .name = 20, +} diff --git a/test/cases/compile_errors/zon/syntax_error.zon b/test/cases/compile_errors/zon/syntax_error.zon new file mode 100644 index 000000000000..237d3445c8eb --- /dev/null +++ b/test/cases/compile_errors/zon/syntax_error.zon @@ -0,0 +1,4 @@ +.{ + .boolean = true + .number = 123, +} diff --git a/test/cases/compile_errors/zon/tuple.zon b/test/cases/compile_errors/zon/tuple.zon new file mode 100644 index 000000000000..4d175090fb4d --- /dev/null +++ b/test/cases/compile_errors/zon/tuple.zon @@ -0,0 +1 @@ +.{ 1.5, 2 } diff --git a/test/cases/compile_errors/zon/type_decl.zon b/test/cases/compile_errors/zon/type_decl.zon new file mode 100644 index 000000000000..bdac762f5475 --- /dev/null +++ b/test/cases/compile_errors/zon/type_decl.zon @@ -0,0 +1,3 @@ +.{ + .foo = struct {}, +} diff --git a/test/cases/compile_errors/zon/type_expr_array.zon b/test/cases/compile_errors/zon/type_expr_array.zon new file mode 100644 index 000000000000..2c76347ae081 --- /dev/null +++ b/test/cases/compile_errors/zon/type_expr_array.zon @@ -0,0 +1 @@ +[3]i32{1, 2, 3} \ No newline at end of file diff --git a/test/cases/compile_errors/zon/type_expr_fn.zon b/test/cases/compile_errors/zon/type_expr_fn.zon new file mode 100644 index 000000000000..8c614f11a8e8 --- /dev/null +++ b/test/cases/compile_errors/zon/type_expr_fn.zon @@ -0,0 +1 @@ +fn foo() void {} diff --git a/test/cases/compile_errors/zon/type_expr_struct.zon b/test/cases/compile_errors/zon/type_expr_struct.zon new file mode 100644 index 000000000000..24d9a64e1168 --- /dev/null +++ b/test/cases/compile_errors/zon/type_expr_struct.zon @@ -0,0 +1 @@ +Vec2{ .x = 1.0, .y = 2.0 } \ No newline at end of file diff --git a/test/cases/compile_errors/zon/type_expr_tuple.zon b/test/cases/compile_errors/zon/type_expr_tuple.zon new file mode 100644 index 000000000000..4281dce0f579 --- /dev/null +++ b/test/cases/compile_errors/zon/type_expr_tuple.zon @@ -0,0 +1 @@ +Vec2{1.0, 2.0} \ No newline at end of file diff --git a/test/cases/compile_errors/zon/unescaped_newline.zon b/test/cases/compile_errors/zon/unescaped_newline.zon new file mode 100644 index 000000000000..f53e156553ac --- /dev/null +++ b/test/cases/compile_errors/zon/unescaped_newline.zon @@ -0,0 +1,2 @@ +"a +b" \ No newline at end of file diff --git a/test/cases/compile_errors/zon/unknown_ident.zon b/test/cases/compile_errors/zon/unknown_ident.zon new file mode 100644 index 000000000000..3e49454f1baa --- /dev/null +++ b/test/cases/compile_errors/zon/unknown_ident.zon @@ -0,0 +1,3 @@ +.{ + .value = truefalse, +} diff --git a/test/cases/compile_errors/zon/vec2.zon b/test/cases/compile_errors/zon/vec2.zon new file mode 100644 index 000000000000..cc4bff59b9a9 --- /dev/null +++ b/test/cases/compile_errors/zon/vec2.zon @@ -0,0 +1 @@ +.{ .x = 1.5, .y = 2 } diff --git a/test/cases/compile_errors/zon/void.zon b/test/cases/compile_errors/zon/void.zon new file mode 100644 index 000000000000..945d0a06385d --- /dev/null +++ b/test/cases/compile_errors/zon/void.zon @@ -0,0 +1 @@ +.{ .foo = {} } diff --git a/test/src/Cases.zig b/test/src/Cases.zig index c49576b2f7eb..44b1a1b3f246 100644 --- a/test/src/Cases.zig +++ b/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), @@ -414,6 +419,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| { @@ -471,7 +477,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, @@ -481,6 +487,8 @@ fn addFromDirInner( .pic = pic, .pie = pie, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + .imports = imports, + .target = resolved_target, }); try cases.append(next); } @@ -620,6 +628,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) { @@ -663,11 +672,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| { @@ -963,6 +982,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; } }; @@ -999,6 +1020,7 @@ const TestManifest = struct { .{ "backend", {} }, .{ "pic", {} }, .{ "pie", {} }, + .{ "imports", {} }, }); const Type = enum { @@ -1021,7 +1043,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; @@ -1099,7 +1121,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); } @@ -1116,7 +1140,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, ','), }; } @@ -1233,6 +1257,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)), } }