From 13c6eb0d71b253cc55a667e33dbdd4932f3710f1 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 14:03:36 -0800 Subject: [PATCH 1/2] compiler,std: implement ZON support This commit allows using ZON (Zig Object Notation) in a few ways. * `@import` can be used to load ZON at comptime and convert it to a normal Zig value. In this case, `@import` must have a result type. * `std.zon.parse` can be used to parse ZON at runtime, akin to the parsing logic in `std.json`. * `std.zon.stringify` can be used to convert arbitrary data structures to ZON at runtime, again akin to `std.json`. --- build.zig | 2 +- lib/compiler/aro/aro/Value.zig | 2 +- lib/compiler/aro/backend/Interner.zig | 4 +- lib/std/math/big/int.zig | 42 +- lib/std/math/big/int_test.zig | 438 +-- lib/std/math/big/rational.zig | 96 +- lib/std/std.zig | 1 + lib/std/zig/AstGen.zig | 29 +- lib/std/zig/Zir.zig | 11 +- lib/std/zig/Zoir.zig | 4 +- lib/std/zig/ZonGen.zig | 208 +- lib/std/zig/string_literal.zig | 102 +- lib/std/zon.zig | 45 + lib/std/zon/parse.zig | 3449 +++++++++++++++++ lib/std/zon/stringify.zig | 2306 +++++++++++ src/Compilation.zig | 31 +- src/InternPool.zig | 18 +- src/Sema.zig | 50 +- src/Sema/LowerZon.zig | 858 ++++ src/Value.zig | 29 +- src/Zcu.zig | 40 +- src/Zcu/PerThread.zig | 5 +- src/codegen/llvm/Builder.zig | 8 +- src/fmt.zig | 4 +- src/main.zig | 2 +- src/print_zir.zig | 12 +- test/behavior/zon.zig | 519 +++ test/behavior/zon/a.zon | 1 + test/behavior/zon/abc-escaped.zon | 1 + test/behavior/zon/abc.zon | 1 + test/behavior/zon/array.zon | 1 + test/behavior/zon/complex.zon | 7 + test/behavior/zon/enum_field.zon | 1 + test/behavior/zon/escaped_enum.zon | 1 + test/behavior/zon/escaped_struct.zon | 2 + test/behavior/zon/false.zon | 4 + test/behavior/zon/floats.zon | 25 + test/behavior/zon/foo.zon | 1 + test/behavior/zon/inf_and_nan.zon | 5 + test/behavior/zon/ints.zon | 40 + test/behavior/zon/multiline_string.zon | 4 + test/behavior/zon/none.zon | 1 + test/behavior/zon/recursive.zon | 1 + test/behavior/zon/slice-abc.zon | 1 + test/behavior/zon/slice-empty.zon | 1 + test/behavior/zon/slice1_no_newline.zon | 1 + test/behavior/zon/some.zon | 1 + test/behavior/zon/string_embedded_null.zon | 1 + test/behavior/zon/true.zon | 1 + test/behavior/zon/tuple.zon | 1 + test/behavior/zon/union1.zon | 1 + test/behavior/zon/union2.zon | 1 + test/behavior/zon/union3.zon | 1 + test/behavior/zon/vec0.zon | 1 + test/behavior/zon/vec1.zon | 1 + test/behavior/zon/vec2.zon | 1 + test/behavior/zon/vec3_bool.zon | 1 + test/behavior/zon/vec3_float.zon | 1 + test/behavior/zon/vec3_int.zon | 1 + test/behavior/zon/vec3_int_opt.zon | 1 + test/behavior/zon/z.zon | 1 + .../compile_errors/@import_zon_addr_slice.zig | 9 + .../compile_errors/@import_zon_array_len.zig | 10 + .../compile_errors/@import_zon_bad_import.zig | 9 + .../compile_errors/@import_zon_bad_type.zig | 125 + .../@import_zon_comptime_inf.zig | 10 + .../@import_zon_comptime_nan.zig | 10 + .../@import_zon_comptime_neg_inf.zig | 10 + .../@import_zon_doc_comment.zig | 9 + .../@import_zon_double_negation_float.zig | 9 + .../@import_zon_double_negation_int.zig | 9 + .../@import_zon_enum_embedded_null.zig | 11 + .../@import_zon_expected_void.zig | 11 + .../@import_zon_invalid_character.zig | 9 + .../@import_zon_invalid_number.zig | 9 + .../@import_zon_invalid_string.zig | 9 + .../@import_zon_leading_zero_in_integer.zig | 10 + .../compile_errors/@import_zon_neg_char.zig | 9 + .../compile_errors/@import_zon_neg_nan.zig | 9 + .../@import_zon_negative_zero.zig | 11 + .../compile_errors/@import_zon_no_rt.zig | 9 + .../@import_zon_number_fail_limits.zig | 10 + .../compile_errors/@import_zon_oob_char_0.zig | 16 + .../compile_errors/@import_zon_oob_char_1.zig | 16 + .../compile_errors/@import_zon_oob_int_0.zig | 16 + .../compile_errors/@import_zon_oob_int_1.zig | 16 + .../compile_errors/@import_zon_oob_int_2.zig | 16 + .../compile_errors/@import_zon_oob_int_3.zig | 10 + .../compile_errors/@import_zon_opt_in_err.zig | 82 + .../@import_zon_opt_in_err_struct.zig | 19 + .../@import_zon_string_as_array.zig | 10 + .../@import_zon_struct_dup_field.zig | 11 + ...import_zon_struct_wrong_comptime_field.zig | 14 + .../@import_zon_syntax_error.zig | 9 + ...@import_zon_tuple_wrong_comptime_field.zig | 14 + .../compile_errors/@import_zon_type_decl.zig | 9 + .../@import_zon_type_expr_array.zig | 10 + .../@import_zon_type_expr_fn.zig | 10 + .../@import_zon_type_expr_struct.zig | 10 + .../@import_zon_type_expr_tuple.zig | 10 + .../@import_zon_type_mismatch.zig | 10 + .../@import_zon_unescaped_newline.zig | 9 + .../@import_zon_unknown_ident.zig | 11 + .../@import_zon_vec_too_few.zig | 10 + .../@import_zon_vec_too_many.zig | 10 + .../@import_zon_vec_wrong_type.zig | 10 + .../cases/compile_errors/@import_zon_void.zig | 10 + test/cases/compile_errors/redundant_try.zig | 4 +- test/cases/compile_errors/zon/addr_slice.zon | 3 + test/cases/compile_errors/zon/array.zon | 1 + test/cases/compile_errors/zon/char_32.zon | 1 + test/cases/compile_errors/zon/desktop.ini | 3 + test/cases/compile_errors/zon/doc_comment.zon | 2 + .../zon/double_negation_float.zon | 1 + .../zon/double_negation_int.zon | 1 + .../compile_errors/zon/enum_embedded_null.zon | 4 + test/cases/compile_errors/zon/hello.zon | 1 + test/cases/compile_errors/zon/inf.zon | 1 + test/cases/compile_errors/zon/int_32.zon | 1 + test/cases/compile_errors/zon/int_neg_33.zon | 1 + .../compile_errors/zon/invalid_character.zon | 1 + .../compile_errors/zon/invalid_number.zon | 1 + .../compile_errors/zon/invalid_string.zon | 1 + .../cases/compile_errors/zon/large_number.zon | 1 + .../zon/leading_zero_in_integer.zon | 1 + test/cases/compile_errors/zon/nan.zon | 1 + test/cases/compile_errors/zon/neg_char.zon | 1 + test/cases/compile_errors/zon/neg_inf.zon | 1 + test/cases/compile_errors/zon/neg_nan.zon | 1 + .../compile_errors/zon/negative_zero.zon | 1 + .../cases/compile_errors/zon/simple_union.zon | 1 + test/cases/compile_errors/zon/struct.zon | 4 + .../compile_errors/zon/struct_dup_field.zon | 4 + .../cases/compile_errors/zon/syntax_error.zon | 4 + test/cases/compile_errors/zon/tuple.zon | 1 + test/cases/compile_errors/zon/type_decl.zon | 3 + .../compile_errors/zon/type_expr_array.zon | 1 + .../cases/compile_errors/zon/type_expr_fn.zon | 1 + .../compile_errors/zon/type_expr_struct.zon | 1 + .../compile_errors/zon/type_expr_tuple.zon | 1 + .../compile_errors/zon/unescaped_newline.zon | 2 + .../compile_errors/zon/unknown_ident.zon | 3 + test/cases/compile_errors/zon/vec2.zon | 1 + test/cases/compile_errors/zon/void.zon | 1 + test/src/Cases.zig | 44 +- 145 files changed, 8786 insertions(+), 434 deletions(-) create mode 100644 lib/std/zon.zig create mode 100644 lib/std/zon/parse.zig create mode 100644 lib/std/zon/stringify.zig create mode 100644 src/Sema/LowerZon.zig create mode 100644 test/behavior/zon.zig create mode 100644 test/behavior/zon/a.zon create mode 100644 test/behavior/zon/abc-escaped.zon create mode 100644 test/behavior/zon/abc.zon create mode 100644 test/behavior/zon/array.zon create mode 100644 test/behavior/zon/complex.zon create mode 100644 test/behavior/zon/enum_field.zon create mode 100644 test/behavior/zon/escaped_enum.zon create mode 100644 test/behavior/zon/escaped_struct.zon create mode 100644 test/behavior/zon/false.zon create mode 100644 test/behavior/zon/floats.zon create mode 100644 test/behavior/zon/foo.zon create mode 100644 test/behavior/zon/inf_and_nan.zon create mode 100644 test/behavior/zon/ints.zon create mode 100644 test/behavior/zon/multiline_string.zon create mode 100644 test/behavior/zon/none.zon create mode 100644 test/behavior/zon/recursive.zon create mode 100644 test/behavior/zon/slice-abc.zon create mode 100644 test/behavior/zon/slice-empty.zon create mode 100644 test/behavior/zon/slice1_no_newline.zon create mode 100644 test/behavior/zon/some.zon create mode 100644 test/behavior/zon/string_embedded_null.zon create mode 100644 test/behavior/zon/true.zon create mode 100644 test/behavior/zon/tuple.zon create mode 100644 test/behavior/zon/union1.zon create mode 100644 test/behavior/zon/union2.zon create mode 100644 test/behavior/zon/union3.zon create mode 100644 test/behavior/zon/vec0.zon create mode 100644 test/behavior/zon/vec1.zon create mode 100644 test/behavior/zon/vec2.zon create mode 100644 test/behavior/zon/vec3_bool.zon create mode 100644 test/behavior/zon/vec3_float.zon create mode 100644 test/behavior/zon/vec3_int.zon create mode 100644 test/behavior/zon/vec3_int_opt.zon create mode 100644 test/behavior/zon/z.zon create mode 100644 test/cases/compile_errors/@import_zon_addr_slice.zig create mode 100644 test/cases/compile_errors/@import_zon_array_len.zig create mode 100644 test/cases/compile_errors/@import_zon_bad_import.zig create mode 100644 test/cases/compile_errors/@import_zon_bad_type.zig create mode 100644 test/cases/compile_errors/@import_zon_comptime_inf.zig create mode 100644 test/cases/compile_errors/@import_zon_comptime_nan.zig create mode 100644 test/cases/compile_errors/@import_zon_comptime_neg_inf.zig create mode 100644 test/cases/compile_errors/@import_zon_doc_comment.zig create mode 100644 test/cases/compile_errors/@import_zon_double_negation_float.zig create mode 100644 test/cases/compile_errors/@import_zon_double_negation_int.zig create mode 100644 test/cases/compile_errors/@import_zon_enum_embedded_null.zig create mode 100644 test/cases/compile_errors/@import_zon_expected_void.zig create mode 100644 test/cases/compile_errors/@import_zon_invalid_character.zig create mode 100644 test/cases/compile_errors/@import_zon_invalid_number.zig create mode 100644 test/cases/compile_errors/@import_zon_invalid_string.zig create mode 100644 test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig create mode 100644 test/cases/compile_errors/@import_zon_neg_char.zig create mode 100644 test/cases/compile_errors/@import_zon_neg_nan.zig create mode 100644 test/cases/compile_errors/@import_zon_negative_zero.zig create mode 100644 test/cases/compile_errors/@import_zon_no_rt.zig create mode 100644 test/cases/compile_errors/@import_zon_number_fail_limits.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_char_0.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_char_1.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_int_0.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_int_1.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_int_2.zig create mode 100644 test/cases/compile_errors/@import_zon_oob_int_3.zig create mode 100644 test/cases/compile_errors/@import_zon_opt_in_err.zig create mode 100644 test/cases/compile_errors/@import_zon_opt_in_err_struct.zig create mode 100644 test/cases/compile_errors/@import_zon_string_as_array.zig create mode 100644 test/cases/compile_errors/@import_zon_struct_dup_field.zig create mode 100644 test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig create mode 100644 test/cases/compile_errors/@import_zon_syntax_error.zig create mode 100644 test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig create mode 100644 test/cases/compile_errors/@import_zon_type_decl.zig create mode 100644 test/cases/compile_errors/@import_zon_type_expr_array.zig create mode 100644 test/cases/compile_errors/@import_zon_type_expr_fn.zig create mode 100644 test/cases/compile_errors/@import_zon_type_expr_struct.zig create mode 100644 test/cases/compile_errors/@import_zon_type_expr_tuple.zig create mode 100644 test/cases/compile_errors/@import_zon_type_mismatch.zig create mode 100644 test/cases/compile_errors/@import_zon_unescaped_newline.zig create mode 100644 test/cases/compile_errors/@import_zon_unknown_ident.zig create mode 100644 test/cases/compile_errors/@import_zon_vec_too_few.zig create mode 100644 test/cases/compile_errors/@import_zon_vec_too_many.zig create mode 100644 test/cases/compile_errors/@import_zon_vec_wrong_type.zig create mode 100644 test/cases/compile_errors/@import_zon_void.zig create mode 100644 test/cases/compile_errors/zon/addr_slice.zon create mode 100644 test/cases/compile_errors/zon/array.zon create mode 100644 test/cases/compile_errors/zon/char_32.zon create mode 100644 test/cases/compile_errors/zon/desktop.ini create mode 100644 test/cases/compile_errors/zon/doc_comment.zon create mode 100644 test/cases/compile_errors/zon/double_negation_float.zon create mode 100644 test/cases/compile_errors/zon/double_negation_int.zon create mode 100644 test/cases/compile_errors/zon/enum_embedded_null.zon create mode 100644 test/cases/compile_errors/zon/hello.zon create mode 100644 test/cases/compile_errors/zon/inf.zon create mode 100644 test/cases/compile_errors/zon/int_32.zon create mode 100644 test/cases/compile_errors/zon/int_neg_33.zon create mode 100644 test/cases/compile_errors/zon/invalid_character.zon create mode 100644 test/cases/compile_errors/zon/invalid_number.zon create mode 100644 test/cases/compile_errors/zon/invalid_string.zon create mode 100644 test/cases/compile_errors/zon/large_number.zon create mode 100644 test/cases/compile_errors/zon/leading_zero_in_integer.zon create mode 100644 test/cases/compile_errors/zon/nan.zon create mode 100644 test/cases/compile_errors/zon/neg_char.zon create mode 100644 test/cases/compile_errors/zon/neg_inf.zon create mode 100644 test/cases/compile_errors/zon/neg_nan.zon create mode 100644 test/cases/compile_errors/zon/negative_zero.zon create mode 100644 test/cases/compile_errors/zon/simple_union.zon create mode 100644 test/cases/compile_errors/zon/struct.zon create mode 100644 test/cases/compile_errors/zon/struct_dup_field.zon create mode 100644 test/cases/compile_errors/zon/syntax_error.zon create mode 100644 test/cases/compile_errors/zon/tuple.zon create mode 100644 test/cases/compile_errors/zon/type_decl.zon create mode 100644 test/cases/compile_errors/zon/type_expr_array.zon create mode 100644 test/cases/compile_errors/zon/type_expr_fn.zon create mode 100644 test/cases/compile_errors/zon/type_expr_struct.zon create mode 100644 test/cases/compile_errors/zon/type_expr_tuple.zon create mode 100644 test/cases/compile_errors/zon/unescaped_newline.zon create mode 100644 test/cases/compile_errors/zon/unknown_ident.zon create mode 100644 test/cases/compile_errors/zon/vec2.zon create mode 100644 test/cases/compile_errors/zon/void.zon diff --git a/build.zig b/build.zig index 1ecc7c7dcb52..1e9d291710c1 100644 --- a/build.zig +++ b/build.zig @@ -406,7 +406,7 @@ pub fn build(b: *std.Build) !void { const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index]; const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" }; - const fmt_exclude_paths = &.{"test/cases"}; + const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" }; const do_fmt = b.addFmt(.{ .paths = fmt_include_paths, .exclude_paths = fmt_exclude_paths, 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 3ffddd1c4c7e..b2cc85b98b67 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -9448,7 +9448,18 @@ fn builtinCall( } else if (str.len == 0) { return astgen.failTok(str_lit_token, "import path cannot be empty", .{}); } - const result = try gz.addStrTok(.import, str.index, str_lit_token); + const res_ty = try ri.rl.resultType(gz, node) orelse .none; + const payload_index = try addExtra(gz.astgen, Zir.Inst.Import{ + .res_ty = res_ty, + .path = str.index, + }); + const result = try gz.add(.{ + .tag = .import, + .data = .{ .pl_tok = .{ + .src_tok = gz.tokenIndexToRelative(str_lit_token), + .payload_index = payload_index, + } }, + }); const gop = try astgen.imports.getOrPut(astgen.gpa, str.index); if (!gop.found_existing) { gop.value_ptr.* = str_lit_token; @@ -11551,9 +11562,21 @@ fn parseStrLit( } } -fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, bytes: []const u8, offset: u32) InnerError { +fn failWithStrLitError( + astgen: *AstGen, + err: std.zig.string_literal.Error, + token: Ast.TokenIndex, + bytes: []const u8, + offset: u32, +) InnerError { const raw_string = bytes[offset..]; - return err.lower(raw_string, offset, AstGen.failOff, .{ astgen, token }); + return failOff( + astgen, + token, + @intCast(offset + err.offset()), + "{}", + .{err.fmt(raw_string)}, + ); } fn failNode( diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 8a19aaa3cbc9..092a354de6f8 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, @@ -1673,7 +1673,7 @@ pub const Inst = struct { .func = .pl_node, .func_inferred = .pl_node, .func_fancy = .pl_node, - .import = .str_tok, + .import = .pl_tok, .int = .int, .int_big = .str, .float = .float, @@ -3841,6 +3841,13 @@ pub const Inst = struct { /// If `.none`, restore unconditionally. operand: Ref, }; + + pub const Import = struct { + /// The result type of the import, or `.none` if none was available. + res_ty: Ref, + /// The import path. + path: NullTerminatedString, + }; }; pub const SpecialProng = enum { none, @"else", under }; diff --git a/lib/std/zig/Zoir.zig b/lib/std/zig/Zoir.zig index 9a9bc419763f..af93d03261ff 100644 --- a/lib/std/zig/Zoir.zig +++ b/lib/std/zig/Zoir.zig @@ -54,7 +54,7 @@ pub const Node = union(enum) { /// A floating-point literal. float_literal: f128, /// A Unicode codepoint literal. - char_literal: u32, + char_literal: u21, /// An enum literal. The string is the literal, i.e. `foo` for `.foo`. enum_literal: NullTerminatedString, /// A string literal. @@ -96,7 +96,7 @@ pub const Node = union(enum) { } } }, .float_literal_small => .{ .float_literal = @as(f32, @bitCast(repr.data)) }, .float_literal => .{ .float_literal = @bitCast(zoir.extra[repr.data..][0..4].*) }, - .char_literal => .{ .char_literal = repr.data }, + .char_literal => .{ .char_literal = @intCast(repr.data) }, .enum_literal => .{ .enum_literal = @enumFromInt(repr.data) }, .string_literal => .{ .string_literal = s: { const start, const len = zoir.extra[repr.data..][0..2].*; diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 7f85f35f0512..c50cf83538c1 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -3,6 +3,8 @@ gpa: Allocator, tree: Ast, +options: Options, + nodes: std.MultiArrayList(Zoir.Node.Repr), extra: std.ArrayListUnmanaged(u32), limbs: std.ArrayListUnmanaged(std.math.big.Limb), @@ -12,12 +14,21 @@ string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.d compile_errors: std.ArrayListUnmanaged(Zoir.CompileError), error_notes: std.ArrayListUnmanaged(Zoir.CompileError.Note), -pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zoir { +pub const Options = struct { + /// When false, string literals are not parsed. `string_literal` nodes will contain empty + /// strings, and errors that normally occur during string parsing will not be raised. + /// + /// `parseStrLit` and `strLitSizeHint` may be used to parse string literals after the fact. + parse_str_lits: bool = true, +}; + +pub fn generate(gpa: Allocator, tree: Ast, options: Options) Allocator.Error!Zoir { assert(tree.mode == .zon); var zg: ZonGen = .{ .gpa = gpa, .tree = tree, + .options = options, .nodes = .empty, .extra = .empty, .limbs = .empty, @@ -250,7 +261,20 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator .block_two_semicolon, .block, .block_semicolon, - => try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}), + => { + const size = switch (node_tags[node]) { + .block_two, .block_two_semicolon => @intFromBool(node_datas[node].lhs != 0) + @intFromBool(node_datas[node].rhs != 0), + .block, .block_semicolon => node_datas[node].rhs - node_datas[node].lhs, + else => unreachable, + }; + if (size == 0) { + try zg.addErrorNodeNotes(node, "void literals are not available in ZON", .{}, &.{ + try zg.errNoteNode(node, "void union payloads can be represented by enum literals", .{}), + }); + } else { + try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}); + } + }, .array_init_one, .array_init_one_comma, @@ -403,58 +427,37 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator .ast_node = node, }); + // For short initializers, track the names on the stack rather than going through gpa. + var sfba_state = std.heap.stackFallback(256, gpa); + const sfba = sfba_state.get(); + var field_names: std.AutoHashMapUnmanaged(Zoir.NullTerminatedString, Ast.TokenIndex) = .empty; + defer field_names.deinit(sfba); + + var reported_any_duplicate = false; + for (full.ast.fields, names_start.., first_elem..) |elem_node, extra_name_idx, elem_dest_node| { const name_token = tree.firstToken(elem_node) - 2; - zg.extra.items[extra_name_idx] = @intFromEnum(zg.identAsString(name_token) catch |err| switch (err) { - error.BadString => undefined, // doesn't matter, there's an error + if (zg.identAsString(name_token)) |name_str| { + zg.extra.items[extra_name_idx] = @intFromEnum(name_str); + const gop = try field_names.getOrPut(sfba, name_str); + if (gop.found_existing and !reported_any_duplicate) { + reported_any_duplicate = true; + const earlier_token = gop.value_ptr.*; + try zg.addErrorTokNotes(earlier_token, "duplicate struct field name", .{}, &.{ + try zg.errNoteTok(name_token, "duplicate name here", .{}), + }); + } + gop.value_ptr.* = name_token; + } else |err| switch (err) { + error.BadString => {}, // there's an error, so it's fine to not populate `zg.extra` error.OutOfMemory => |e| return e, - }); + } try zg.expr(elem_node, @enumFromInt(elem_dest_node)); } }, } } -fn parseStrLit(zg: *ZonGen, token: Ast.TokenIndex, offset: u32) !u32 { - const raw_string = zg.tree.tokenSlice(token)[offset..]; - const start = zg.string_bytes.items.len; - switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) { - .success => return @intCast(start), - .failure => |err| { - try zg.lowerStrLitError(err, token, raw_string, offset); - return error.BadString; - }, - } -} - -fn parseMultilineStrLit(zg: *ZonGen, node: Ast.Node.Index) !u32 { - const gpa = zg.gpa; - const tree = zg.tree; - const string_bytes = &zg.string_bytes; - - const first_tok, const last_tok = bounds: { - const node_data = tree.nodes.items(.data)[node]; - break :bounds .{ node_data.lhs, node_data.rhs }; - }; - - const str_index: u32 = @intCast(string_bytes.items.len); - - // First line: do not append a newline. - { - const line_bytes = tree.tokenSlice(first_tok)[2..]; - try string_bytes.appendSlice(gpa, line_bytes); - } - // Following lines: each line prepends a newline. - for (first_tok + 1..last_tok + 1) |tok_idx| { - const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..]; - try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1); - string_bytes.appendAssumeCapacity('\n'); - string_bytes.appendSliceAssumeCapacity(line_bytes); - } - - return @intCast(str_index); -} - fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 { const tree = zg.tree; assert(tree.tokens.items(.tag)[ident_token] == .identifier); @@ -464,7 +467,18 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 { try zg.string_bytes.appendSlice(zg.gpa, ident_name); return @intCast(start); } else { - const start = try zg.parseStrLit(ident_token, 1); + const offset = 1; + const start: u32 = @intCast(zg.string_bytes.items.len); + const raw_string = zg.tree.tokenSlice(ident_token)[offset..]; + try zg.string_bytes.ensureUnusedCapacity(zg.gpa, raw_string.len); + switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) { + .success => {}, + .failure => |err| { + try zg.lowerStrLitError(err, ident_token, raw_string, offset); + return error.BadString; + }, + } + const slice = zg.string_bytes.items[start..]; if (mem.indexOfScalar(u8, slice, 0) != null) { try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{}); @@ -477,19 +491,93 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 { } } +/// Estimates the size of a string node without parsing it. +pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize { + switch (tree.nodes.items(.tag)[node]) { + // Parsed string literals are typically around the size of the raw strings. + .string_literal => { + const token = tree.nodes.items(.main_token)[node]; + const raw_string = tree.tokenSlice(token); + return raw_string.len; + }, + // Multiline string literal lengths can be computed exactly. + .multiline_string_literal => { + const first_tok, const last_tok = bounds: { + const node_data = tree.nodes.items(.data)[node]; + break :bounds .{ node_data.lhs, node_data.rhs }; + }; + + var size = tree.tokenSlice(first_tok)[2..].len; + for (first_tok + 1..last_tok + 1) |tok_idx| { + size += 1; // Newline + size += tree.tokenSlice(@intCast(tok_idx))[2..].len; + } + return size; + }, + else => unreachable, + } +} + +/// Parses the given node as a string literal. +pub fn parseStrLit( + tree: Ast, + node: Ast.Node.Index, + writer: anytype, +) error{OutOfMemory}!std.zig.string_literal.Result { + switch (tree.nodes.items(.tag)[node]) { + .string_literal => { + const token = tree.nodes.items(.main_token)[node]; + const raw_string = tree.tokenSlice(token); + return std.zig.string_literal.parseWrite(writer, raw_string); + }, + .multiline_string_literal => { + const first_tok, const last_tok = bounds: { + const node_data = tree.nodes.items(.data)[node]; + break :bounds .{ node_data.lhs, node_data.rhs }; + }; + + // First line: do not append a newline. + { + const line_bytes = tree.tokenSlice(first_tok)[2..]; + try writer.writeAll(line_bytes); + } + + // Following lines: each line prepends a newline. + for (first_tok + 1..last_tok + 1) |tok_idx| { + const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..]; + try writer.writeByte('\n'); + try writer.writeAll(line_bytes); + } + + return .success; + }, + // Node must represent a string + else => unreachable, + } +} + const StringLiteralResult = union(enum) { nts: Zoir.NullTerminatedString, slice: struct { start: u32, len: u32 }, }; fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult { + if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } }; + const gpa = zg.gpa; const string_bytes = &zg.string_bytes; - const str_index = switch (zg.tree.nodes.items(.tag)[str_node]) { - .string_literal => try zg.parseStrLit(zg.tree.nodes.items(.main_token)[str_node], 0), - .multiline_string_literal => try zg.parseMultilineStrLit(str_node), - else => unreachable, - }; + const str_index: u32 = @intCast(zg.string_bytes.items.len); + const size_hint = strLitSizeHint(zg.tree, str_node); + try string_bytes.ensureUnusedCapacity(zg.gpa, size_hint); + switch (try parseStrLit(zg.tree, str_node, zg.string_bytes.writer(zg.gpa))) { + .success => {}, + .failure => |err| { + const token = zg.tree.nodes.items(.main_token)[str_node]; + const raw_string = zg.tree.tokenSlice(token); + try zg.lowerStrLitError(err, token, raw_string, 0); + return error.BadString; + }, + } const key: []const u8 = string_bytes.items[str_index..]; if (std.mem.indexOfScalar(u8, key, 0) != null) return .{ .slice = .{ .start = str_index, @@ -540,7 +628,7 @@ fn numberLiteral(zg: *ZonGen, num_node: Ast.Node.Index, src_node: Ast.Node.Index if (unsigned_num == 0 and sign == .negative) { try zg.addErrorTokNotes(num_token, "integer literal '-0' is ambiguous", .{}, &.{ try zg.errNoteTok(num_token, "use '0' for an integer zero", .{}), - try zg.errNoteTok(num_token, "use '-0.0' for a flaoting-point signed zero", .{}), + try zg.errNoteTok(num_token, "use '-0.0' for a floating-point signed zero", .{}), }); return; } @@ -679,8 +767,20 @@ fn setNode(zg: *ZonGen, dest: Zoir.Node.Index, repr: Zoir.Node.Repr) void { zg.nodes.set(@intFromEnum(dest), repr); } -fn lowerStrLitError(zg: *ZonGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, raw_string: []const u8, offset: u32) Allocator.Error!void { - return err.lower(raw_string, offset, ZonGen.addErrorTokOff, .{ zg, token }); +fn lowerStrLitError( + zg: *ZonGen, + err: std.zig.string_literal.Error, + token: Ast.TokenIndex, + raw_string: []const u8, + offset: u32, +) Allocator.Error!void { + return ZonGen.addErrorTokOff( + zg, + token, + @intCast(offset + err.offset()), + "{}", + .{err.fmt(raw_string)}, + ); } fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) Allocator.Error!void { diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 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..252331057a8c --- /dev/null +++ b/lib/std/zon.zig @@ -0,0 +1,45 @@ +//! ZON parsing and stringification. +//! +//! ZON ("Zig Object Notation") is a textual file format. Outside of `nan` and `inf` literals, ZON's +//! grammar is a subset of Zig's. +//! +//! Supported Zig primitives: +//! * boolean literals +//! * number literals (including `nan` and `inf`) +//! * character literals +//! * enum literals +//! * `null` literals +//! * string literals +//! * multiline string literals +//! +//! Supported Zig container types: +//! * anonymous struct literals +//! * anonymous tuple literals +//! +//! Here is an example ZON object: +//! ``` +//! .{ +//! .a = 1.5, +//! .b = "hello, world!", +//! .c = .{ true, false }, +//! .d = .{ 1, 2, 3 }, +//! } +//! ``` +//! +//! Individual primitives are also valid ZON, for example: +//! ``` +//! "This string is a valid ZON object." +//! ``` +//! +//! ZON may not contain type names. +//! +//! ZON does not have syntax for pointers, but the parsers will allocate as needed to match the +//! given Zig types. Similarly, the serializer will traverse pointers. + +pub const parse = @import("zon/parse.zig"); +pub const stringify = @import("zon/stringify.zig"); + +test { + _ = parse; + _ = stringify; +} diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig new file mode 100644 index 000000000000..daf83d0bbdb0 --- /dev/null +++ b/lib/std/zon/parse.zig @@ -0,0 +1,3449 @@ +//! The simplest way to parse ZON at runtime is to use `fromSlice`. If you need to parse ZON at +//! compile time, you may use `@import`. +//! +//! Parsing from individual Zoir nodes is also available: +//! * `fromZoir` +//! * `fromZoirNode` +//! +//! For lower level control, it is possible to operate on `std.zig.Zoir` directly. + +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const Ast = std.zig.Ast; +const Zoir = std.zig.Zoir; +const ZonGen = std.zig.ZonGen; +const TokenIndex = std.zig.Ast.TokenIndex; +const Base = std.zig.number_literal.Base; +const StrLitErr = std.zig.string_literal.Error; +const NumberLiteralError = std.zig.number_literal.Error; +const assert = std.debug.assert; +const ArrayListUnmanaged = std.ArrayListUnmanaged; + +/// Rename when adding or removing support for a type. +const valid_types = {}; + +/// Configuration for the runtime parser. +pub const Options = struct { + /// If true, unknown fields do not error. + ignore_unknown_fields: bool = false, + /// If true, the parser cleans up partially parsed values on error. This requires some extra + /// bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because + /// you're using arena allocation.) + free_on_error: bool = true, +}; + +pub const Error = union(enum) { + zoir: Zoir.CompileError, + type_check: Error.TypeCheckFailure, + + pub const Note = union(enum) { + zoir: Zoir.CompileError.Note, + type_check: TypeCheckFailure.Note, + + pub const Iterator = struct { + index: usize = 0, + err: Error, + status: *const Status, + + pub fn next(self: *@This()) ?Note { + switch (self.err) { + .zoir => |err| { + if (self.index >= err.note_count) return null; + const zoir = self.status.zoir.?; + const note = err.getNotes(zoir)[self.index]; + self.index += 1; + return .{ .zoir = note }; + }, + .type_check => |err| { + if (self.index >= err.getNoteCount()) return null; + const note = err.getNote(self.index); + self.index += 1; + return .{ .type_check = note }; + }, + } + } + }; + + fn formatMessage( + self: []const u8, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = f; + _ = options; + + // Just writes the string for now, but we're keeping this behind a formatter so we have + // the option to extend it in the future to print more advanced messages (like `Error` + // does) without breaking the API. + try writer.writeAll(self); + } + + pub fn fmtMessage(self: Note, status: *const Status) std.fmt.Formatter(Note.formatMessage) { + return .{ .data = switch (self) { + .zoir => |note| note.msg.get(status.zoir.?), + .type_check => |note| note.msg, + } }; + } + + pub fn getLocation(self: Note, status: *const Status) Ast.Location { + const ast = status.ast.?; + switch (self) { + .zoir => |note| return zoirErrorLocation(ast, note.token, note.node_or_offset), + .type_check => |note| return ast.tokenLocation(note.offset, note.token), + } + } + }; + + pub const Iterator = struct { + index: usize = 0, + status: *const Status, + + pub fn next(self: *@This()) ?Error { + const zoir = self.status.zoir orelse return null; + + if (self.index < zoir.compile_errors.len) { + const result: Error = .{ .zoir = zoir.compile_errors[self.index] }; + self.index += 1; + return result; + } + + if (self.status.type_check) |err| { + if (self.index == zoir.compile_errors.len) { + const result: Error = .{ .type_check = err }; + self.index += 1; + return result; + } + } + + return null; + } + }; + + const TypeCheckFailure = struct { + const Note = struct { + token: Ast.TokenIndex, + offset: u32, + msg: []const u8, + owned: bool, + + fn deinit(self: @This(), gpa: Allocator) void { + if (self.owned) gpa.free(self.msg); + } + }; + + message: []const u8, + owned: bool, + token: Ast.TokenIndex, + offset: u32, + note: ?@This().Note, + + fn deinit(self: @This(), gpa: Allocator) void { + if (self.note) |note| note.deinit(gpa); + if (self.owned) gpa.free(self.message); + } + + fn getNoteCount(self: @This()) usize { + return @intFromBool(self.note != null); + } + + fn getNote(self: @This(), index: usize) @This().Note { + assert(index == 0); + return self.note.?; + } + }; + + const FormatMessage = struct { + err: Error, + status: *const Status, + }; + + fn formatMessage( + self: FormatMessage, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = f; + _ = options; + switch (self.err) { + .zoir => |err| try writer.writeAll(err.msg.get(self.status.zoir.?)), + .type_check => |tc| try writer.writeAll(tc.message), + } + } + + pub fn fmtMessage(self: @This(), status: *const Status) std.fmt.Formatter(formatMessage) { + return .{ .data = .{ + .err = self, + .status = status, + } }; + } + + pub fn getLocation(self: @This(), status: *const Status) Ast.Location { + const ast = status.ast.?; + return switch (self) { + .zoir => |err| return zoirErrorLocation( + status.ast.?, + err.token, + err.node_or_offset, + ), + .type_check => |err| return ast.tokenLocation(err.offset, err.token), + }; + } + + pub fn iterateNotes(self: @This(), status: *const Status) Note.Iterator { + return .{ .err = self, .status = status }; + } + + fn zoirErrorLocation(ast: Ast, maybe_token: Ast.TokenIndex, node_or_offset: u32) Ast.Location { + if (maybe_token == Zoir.CompileError.invalid_token) { + const main_tokens = ast.nodes.items(.main_token); + const ast_node = node_or_offset; + const token = main_tokens[ast_node]; + return ast.tokenLocation(0, token); + } else { + var location = ast.tokenLocation(0, maybe_token); + location.column += node_or_offset; + return location; + } + } +}; + +/// Information about the success or failure of a parse. +pub const Status = struct { + ast: ?Ast = null, + zoir: ?Zoir = null, + type_check: ?Error.TypeCheckFailure = null, + + fn assertEmpty(self: Status) void { + assert(self.ast == null); + assert(self.zoir == null); + assert(self.type_check == null); + } + + pub fn deinit(self: *Status, gpa: Allocator) void { + if (self.ast) |*ast| ast.deinit(gpa); + if (self.zoir) |*zoir| zoir.deinit(gpa); + if (self.type_check) |tc| tc.deinit(gpa); + self.* = undefined; + } + + pub fn iterateErrors(self: *const Status) Error.Iterator { + return .{ .status = self }; + } + + pub fn format( + self: *const @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = fmt; + _ = options; + var errors = self.iterateErrors(); + while (errors.next()) |err| { + const loc = err.getLocation(self); + const msg = err.fmtMessage(self); + try writer.print("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, msg }); + + var notes = err.iterateNotes(self); + while (notes.next()) |note| { + const note_loc = note.getLocation(self); + const note_msg = note.fmtMessage(self); + try writer.print("{}:{}: note: {s}\n", .{ + note_loc.line + 1, + note_loc.column + 1, + note_msg, + }); + } + } + } +}; + +/// Parses the given slice as ZON. +/// +/// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is +/// invalid or can not be deserialized into type `T`. +/// +/// When the parser returns `error.ParseZon`, it will also store a human readable explanation in +/// `status` if non null. If status is not null, it must be initialized to `.{}`. +pub fn fromSlice( + /// The type to deserialize into. May not be or contain any of the following types: + /// * Any comptime-only type, except in a comptime field + /// * `type` + /// * `void`, except as a union payload + /// * `noreturn` + /// * An error set/error union + /// * A many-pointer or C-pointer + /// * An opaque type, including `anyopaque` + /// * An async frame type, including `anyframe` and `anyframe->T` + /// * A function + /// + /// All other types are valid. Unsupported types will fail at compile time. + T: type, + gpa: Allocator, + source: [:0]const u8, + status: ?*Status, + options: Options, +) error{ OutOfMemory, ParseZon }!T { + if (status) |s| s.assertEmpty(); + + var ast = try std.zig.Ast.parse(gpa, source, .zon); + defer if (status == null) ast.deinit(gpa); + if (status) |s| s.ast = ast; + + // If there's no status, Zoir exists for the lifetime of this function. If there is a status, + // ownership is transferred to status. + var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); + defer if (status == null) zoir.deinit(gpa); + + if (status) |s| s.* = .{}; + return fromZoir(T, gpa, ast, zoir, status, options); +} + +/// Like `fromSlice`, but operates on `Zoir` instead of ZON source. +pub fn fromZoir( + T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + options: Options, +) error{ OutOfMemory, ParseZon }!T { + return fromZoirNode(T, gpa, ast, zoir, .root, status, options); +} + +/// Like `fromZoir`, but the parse starts on `node` instead of root. +pub fn fromZoirNode( + T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + node: Zoir.Node.Index, + status: ?*Status, + options: Options, +) error{ OutOfMemory, ParseZon }!T { + comptime assert(canParseType(T)); + + if (status) |s| { + s.assertEmpty(); + s.ast = ast; + s.zoir = zoir; + } + + if (zoir.hasCompileErrors()) { + return error.ParseZon; + } + + var parser: Parser = .{ + .gpa = gpa, + .ast = ast, + .zoir = zoir, + .options = options, + .status = status, + }; + + return parser.parseExpr(T, node); +} + +/// Frees ZON values. +/// +/// Provided for convenience, you may also free these values on your own using the same allocator +/// passed into the parser. +/// +/// Asserts at comptime that sufficient information is available via the type system to free this +/// value. Untagged unions, for example, will fail this assert. +pub fn free(gpa: Allocator, value: anytype) void { + const Value = @TypeOf(value); + + _ = valid_types; + switch (@typeInfo(Value)) { + .bool, .int, .float, .@"enum" => {}, + .pointer => |pointer| { + switch (pointer.size) { + .one => { + free(gpa, value.*); + gpa.destroy(value); + }, + .slice => { + for (value) |item| { + free(gpa, item); + } + gpa.free(value); + }, + .many, .c => comptime unreachable, + } + }, + .array => for (value) |item| { + free(gpa, item); + }, + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { + free(gpa, @field(value, field.name)); + }, + .@"union" => |@"union"| if (@"union".tag_type == null) { + if (comptime requiresAllocator(Value)) unreachable; + } else switch (value) { + inline else => |_, tag| { + free(gpa, @field(value, @tagName(tag))); + }, + }, + .optional => if (value) |some| { + free(gpa, some); + }, + .vector => |vector| for (0..vector.len) |i| free(gpa, value[i]), + .void => {}, + else => comptime unreachable, + } +} + +fn requiresAllocator(T: type) bool { + _ = valid_types; + return switch (@typeInfo(T)) { + .pointer => true, + .array => |array| return array.len > 0 and requiresAllocator(array.child), + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .@"union" => |@"union"| inline for (@"union".fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .optional => |optional| requiresAllocator(optional.child), + .vector => |vector| return vector.len > 0 and requiresAllocator(vector.child), + else => false, + }; +} + +const Parser = struct { + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + options: Options, + + fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T { + return self.parseExprInner(T, node) catch |err| switch (err) { + error.WrongType => return self.failExpectedType(T, node), + else => |e| return e, + }; + } + + fn parseExprInner( + self: *@This(), + T: type, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory, WrongType }!T { + switch (@typeInfo(T)) { + .optional => |optional| if (node.get(self.zoir) == .null) { + return null; + } else { + return try self.parseExprInner(optional.child, node); + }, + .bool => return self.parseBool(node), + .int => return self.parseInt(T, node), + .float => return self.parseFloat(T, node), + .@"enum" => return self.parseEnumLiteral(T, node), + .pointer => |pointer| switch (pointer.size) { + .one => { + const result = try self.gpa.create(pointer.child); + errdefer self.gpa.destroy(result); + result.* = try self.parseExprInner(pointer.child, node); + return result; + }, + .slice => return self.parseSlicePointer(T, node), + else => comptime unreachable, + }, + .array => return self.parseArray(T, node), + .@"struct" => |@"struct"| if (@"struct".is_tuple) + return self.parseTuple(T, node) + else + return self.parseStruct(T, node), + .@"union" => return self.parseUnion(T, node), + .vector => return self.parseVector(T, node), + + else => comptime unreachable, + } + } + + /// Prints a message of the form `expected T` where T is first converted to a ZON type. For + /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just + /// referred to by the type of container. + fn failExpectedType( + self: @This(), + T: type, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory } { + @branchHint(.cold); + return self.failExpectedTypeInner(T, false, node); + } + + fn failExpectedTypeInner( + self: @This(), + T: type, + opt: bool, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory } { + _ = valid_types; + switch (@typeInfo(T)) { + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + if (opt) { + return self.failNode(node, "expected optional tuple"); + } else { + return self.failNode(node, "expected tuple"); + } + } else { + if (opt) { + return self.failNode(node, "expected optional struct"); + } else { + return self.failNode(node, "expected struct"); + } + }, + .@"union" => if (opt) { + return self.failNode(node, "expected optional union"); + } else { + return self.failNode(node, "expected union"); + }, + .array => if (opt) { + return self.failNode(node, "expected optional array"); + } else { + return self.failNode(node, "expected array"); + }, + .pointer => |pointer| switch (pointer.size) { + .one => return self.failExpectedTypeInner(pointer.child, opt, node), + .slice => { + if (pointer.child == u8 and + pointer.is_const and + (pointer.sentinel() == null or pointer.sentinel() == 0) and + pointer.alignment == 1) + { + if (opt) { + return self.failNode(node, "expected optional string"); + } else { + return self.failNode(node, "expected string"); + } + } else { + if (opt) { + return self.failNode(node, "expected optional array"); + } else { + return self.failNode(node, "expected array"); + } + } + }, + else => comptime unreachable, + }, + .vector, .bool, .int, .float => if (opt) { + return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)}); + } else { + return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}); + }, + .@"enum" => if (opt) { + return self.failNode(node, "expected optional enum literal"); + } else { + return self.failNode(node, "expected enum literal"); + }, + .optional => |optional| { + return self.failExpectedTypeInner(optional.child, true, node); + }, + else => comptime unreachable, + } + } + + fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { + switch (node.get(self.zoir)) { + .true => return true, + .false => return false, + else => return error.WrongType, + } + } + + fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T { + switch (node.get(self.zoir)) { + .int_literal => |int| switch (int) { + .small => |val| return std.math.cast(T, val) orelse + self.failCannotRepresent(T, node), + .big => |val| return val.toInt(T) catch + self.failCannotRepresent(T, node), + }, + .float_literal => |val| return intFromFloatExact(T, val) orelse + self.failCannotRepresent(T, node), + + .char_literal => |val| return std.math.cast(T, val) orelse + self.failCannotRepresent(T, node), + else => return error.WrongType, + } + } + + fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T { + switch (node.get(self.zoir)) { + .int_literal => |int| switch (int) { + .small => |val| return @floatFromInt(val), + .big => |val| return val.toFloat(T), + }, + .float_literal => |val| return @floatCast(val), + .pos_inf => return std.math.inf(T), + .neg_inf => return -std.math.inf(T), + .nan => return std.math.nan(T), + .char_literal => |val| return @floatFromInt(val), + else => return error.WrongType, + } + } + + fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T { + switch (node.get(self.zoir)) { + .enum_literal => |field_name| { + // Create a comptime string map for the enum fields + const enum_fields = @typeInfo(T).@"enum".fields; + comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; + inline for (enum_fields, 0..) |field, i| { + kvs_list[i] = .{ field.name, @enumFromInt(field.value) }; + } + const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); + + // Get the tag if it exists + const field_name_str = field_name.get(self.zoir); + return enum_tags.get(field_name_str) orelse + self.failUnexpected(T, "enum literal", node, null, field_name_str); + }, + else => return error.WrongType, + } + } + + fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { + switch (node.get(self.zoir)) { + .string_literal => return self.parseString(T, node), + .array_literal => |nodes| return self.parseSlice(T, nodes), + .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }), + else => return error.WrongType, + } + } + + fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T { + const ast_node = node.getAstNode(self.zoir); + const pointer = @typeInfo(T).pointer; + var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); + if (pointer.sentinel() != null) size_hint += 1; + + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint); + defer buf.deinit(self.gpa); + switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) { + .success => {}, + .failure => |err| { + const token = self.ast.nodes.items(.main_token)[ast_node]; + const raw_string = self.ast.tokenSlice(token); + return self.failTokenFmt(token, @intCast(err.offset()), "{s}", .{err.fmt(raw_string)}); + }, + } + + if (pointer.child != u8 or + pointer.size != .slice or + !pointer.is_const or + (pointer.sentinel() != null and pointer.sentinel() != 0) or + pointer.alignment != 1) + { + return error.WrongType; + } + + if (pointer.sentinel() != null) { + return buf.toOwnedSliceSentinel(self.gpa, 0); + } else { + return buf.toOwnedSlice(self.gpa); + } + } + + fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T { + const pointer = @typeInfo(T).pointer; + + // Make sure we're working with a slice + switch (pointer.size) { + .slice => {}, + .one, .many, .c => comptime unreachable, + } + + // Allocate the slice + const slice = try self.gpa.allocWithOptions( + pointer.child, + nodes.len, + pointer.alignment, + pointer.sentinel(), + ); + errdefer self.gpa.free(slice); + + // Parse the elements and return the slice + for (slice, 0..) |*elem, i| { + errdefer if (self.options.free_on_error) { + for (slice[0..i]) |item| { + free(self.gpa, item); + } + }; + elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i))); + } + + return slice; + } + + fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + const array_info = @typeInfo(T).array; + + // Check if the size matches + if (nodes.len < array_info.len) { + return self.failNodeFmt( + node, + "expected {} array elements; found {}", + .{ array_info.len, nodes.len }, + ); + } else if (nodes.len > array_info.len) { + return self.failNodeFmt( + nodes.at(array_info.len), + "index {} outside of array of length {}", + .{ array_info.len, array_info.len }, + ); + } + + // Parse the elements and return the array + var result: T = undefined; + for (&result, 0..) |*elem, i| { + // If we fail to parse this field, free all fields before it + errdefer if (self.options.free_on_error) { + for (result[0..i]) |item| { + free(self.gpa, item); + } + }; + + elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i))); + } + return result; + } + + fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T { + const repr = node.get(self.zoir); + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { + .struct_literal => |nodes| nodes, + .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, + else => return error.WrongType, + }; + + const field_infos = @typeInfo(T).@"struct".fields; + + // Build a map from field name to index. + // The special value `comptime_field` indicates that this is actually a comptime field. + const comptime_field = std.math.maxInt(usize); + const field_indices: std.StaticStringMap(usize) = comptime b: { + var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + for (&kvs_list, field_infos, 0..) |*kv, field, i| { + kv.* = .{ field.name, if (field.is_comptime) comptime_field else i }; + } + break :b .initComptime(kvs_list); + }; + + // Parse the struct + var result: T = undefined; + var field_found: [field_infos.len]bool = @splat(false); + + // If we fail partway through, free all already initialized fields + var initialized: usize = 0; + errdefer if (self.options.free_on_error and field_infos.len > 0) { + for (fields.names[0..initialized]) |name_runtime| { + switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { + inline 0...(field_infos.len - 1) => |name_index| { + const name = field_infos[name_index].name; + free(self.gpa, @field(result, name)); + }, + else => unreachable, // Can't be out of bounds + } + } + }; + + // Fill in the fields we found + for (0..fields.names.len) |i| { + const name = fields.names[i].get(self.zoir); + const field_index = field_indices.get(name) orelse { + if (self.options.ignore_unknown_fields) continue; + return self.failUnexpected(T, "field", node, i, name); + }; + if (field_index == comptime_field) { + return self.failComptimeField(node, i); + } + + // Mark the field as found. Assert that the found array is not zero length to satisfy + // the type checker (it can't be since we made it into an iteration of this loop.) + if (field_found.len == 0) unreachable; + field_found[field_index] = true; + + switch (field_index) { + inline 0...(field_infos.len - 1) => |j| { + if (field_infos[j].is_comptime) unreachable; + + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + fields.vals.at(@intCast(i)), + ); + }, + else => unreachable, // Can't be out of bounds + } + + initialized += 1; + } + + // Fill in any missing default fields + inline for (field_found, 0..) |found, i| { + if (!found) { + const field_info = field_infos[i]; + if (field_info.default_value_ptr) |default| { + const typed: *const field_info.type = @ptrCast(@alignCast(default)); + @field(result, field_info.name) = typed.*; + } else { + return self.failNodeFmt( + node, + "missing required field {s}", + .{field_infos[i].name}, + ); + } + } + } + + return result; + } + + fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + var result: T = undefined; + const field_infos = @typeInfo(T).@"struct".fields; + + if (nodes.len > field_infos.len) { + return self.failNodeFmt( + nodes.at(field_infos.len), + "index {} outside of tuple length {}", + .{ field_infos.len, field_infos.len }, + ); + } + + inline for (0..field_infos.len) |i| { + // Check if we're out of bounds + if (i >= nodes.len) { + if (field_infos[i].default_value_ptr) |default| { + const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); + @field(result, field_infos[i].name) = typed.*; + } else { + return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); + } + } else { + // If we fail to parse this field, free all fields before it + errdefer if (self.options.free_on_error) { + inline for (0..i) |j| { + if (j >= i) break; + free(self.gpa, result[j]); + } + }; + + if (field_infos[i].is_comptime) { + return self.failComptimeField(node, i); + } else { + result[i] = try self.parseExpr(field_infos[i].type, nodes.at(i)); + } + } + } + + return result; + } + + fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T { + const @"union" = @typeInfo(T).@"union"; + const field_infos = @"union".fields; + + if (field_infos.len == 0) comptime unreachable; + + // Gather info on the fields + const field_indices = b: { + comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + inline for (field_infos, 0..) |field, i| { + kvs_list[i] = .{ field.name, i }; + } + break :b std.StaticStringMap(usize).initComptime(kvs_list); + }; + + // Parse the union + switch (node.get(self.zoir)) { + .enum_literal => |field_name| { + // The union must be tagged for an enum literal to coerce to it + if (@"union".tag_type == null) { + return error.WrongType; + } + + // Get the index of the named field. We don't use `parseEnum` here as + // the order of the enum and the order of the union might not match! + const field_index = b: { + const field_name_str = field_name.get(self.zoir); + break :b field_indices.get(field_name_str) orelse + return self.failUnexpected(T, "field", node, null, field_name_str); + }; + + // Initialize the union from the given field. + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + // Fail if the field is not void + if (field_infos[i].type != void) + return self.failNode(node, "expected union"); + + // Instantiate the union + return @unionInit(T, field_infos[i].name, {}); + }, + else => unreachable, // Can't be out of bounds + } + }, + .struct_literal => |struct_fields| { + if (struct_fields.names.len != 1) { + return error.WrongType; + } + + // Fill in the field we found + const field_name = struct_fields.names[0]; + const field_name_str = field_name.get(self.zoir); + const field_val = struct_fields.vals.at(0); + const field_index = field_indices.get(field_name_str) orelse + return self.failUnexpected(T, "field", node, 0, field_name_str); + + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + if (field_infos[i].type == void) { + return self.failNode(field_val, "expected type 'void'"); + } else { + const value = try self.parseExpr(field_infos[i].type, field_val); + return @unionInit(T, field_infos[i].name, value); + } + }, + else => unreachable, // Can't be out of bounds + } + }, + else => return error.WrongType, + } + } + + fn parseVector( + self: *@This(), + T: type, + node: Zoir.Node.Index, + ) !T { + const vector_info = @typeInfo(T).vector; + + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + var result: T = undefined; + + if (nodes.len != vector_info.len) { + return self.failNodeFmt( + node, + "expected {} vector elements; found {}", + .{ vector_info.len, nodes.len }, + ); + } + + for (0..vector_info.len) |i| { + errdefer for (0..i) |j| free(self.gpa, result[j]); + result[i] = try self.parseExpr(vector_info.child, nodes.at(@intCast(i))); + } + + return result; + } + + fn failTokenFmt( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failTokenFmtNote(token, offset, fmt, args, null); + } + + fn failTokenFmtNote( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, + note: ?Error.TypeCheckFailure.Note, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + comptime assert(args.len > 0); + if (self.status) |s| s.type_check = .{ + .token = token, + .offset = offset, + .message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| { + if (note) |n| n.deinit(self.gpa); + return err; + }, + .owned = true, + .note = note, + }; + return error.ParseZon; + } + + fn failNodeFmt( + self: @This(), + node: Zoir.Node.Index, + comptime fmt: []const u8, + args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failTokenFmt(token, 0, fmt, args); + } + + fn failToken( + self: @This(), + failure: Error.TypeCheckFailure, + ) error{ParseZon} { + @branchHint(.cold); + if (self.status) |s| s.type_check = failure; + return error.ParseZon; + } + + fn failNode( + self: @This(), + node: Zoir.Node.Index, + message: []const u8, + ) error{ParseZon} { + @branchHint(.cold); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failToken(.{ + .token = token, + .offset = 0, + .message = message, + .owned = false, + .note = null, + }); + } + + fn failCannotRepresent( + self: @This(), + T: type, + node: Zoir.Node.Index, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); + } + + fn failUnexpected( + self: @This(), + T: type, + item_kind: []const u8, + node: Zoir.Node.Index, + field: ?usize, + name: []const u8, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const token = if (field) |f| b: { + var buf: [2]Ast.Node.Index = undefined; + const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; + const field_node = struct_init.ast.fields[f]; + break :b self.ast.firstToken(field_node) - 2; + } else b: { + const main_tokens = self.ast.nodes.items(.main_token); + break :b main_tokens[node.getAstNode(self.zoir)]; + }; + switch (@typeInfo(T)) { + inline .@"struct", .@"union", .@"enum" => |info| { + const note: Error.TypeCheckFailure.Note = if (info.fields.len == 0) b: { + break :b .{ + .token = token, + .offset = 0, + .msg = "none expected", + .owned = false, + }; + } else b: { + const msg = "supported: "; + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64); + defer buf.deinit(self.gpa); + const writer = buf.writer(self.gpa); + try writer.writeAll(msg); + inline for (info.fields, 0..) |field_info, i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("'{p_}'", .{std.zig.fmtId(field_info.name)}); + } + break :b .{ + .token = token, + .offset = 0, + .msg = try buf.toOwnedSlice(self.gpa), + .owned = true, + }; + }; + return self.failTokenFmtNote( + token, + 0, + "unexpected {s} '{s}'", + .{ item_kind, name }, + note, + ); + }, + else => comptime unreachable, + } + } + + // Technically we could do this if we were willing to do a deep equal to verify + // the value matched, but doing so doesn't seem to support any real use cases + // so isn't worth the complexity at the moment. + fn failComptimeField( + self: @This(), + node: Zoir.Node.Index, + field: usize, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const ast_node = node.getAstNode(self.zoir); + var buf: [2]Ast.Node.Index = undefined; + const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: { + const field_node = struct_init.ast.fields[field]; + break :b self.ast.firstToken(field_node); + } else b: { + const array_init = self.ast.fullArrayInit(&buf, ast_node).?; + const value_node = array_init.ast.elements[field]; + break :b self.ast.firstToken(value_node); + }; + return self.failToken(.{ + .token = token, + .offset = 0, + .message = "cannot initialize comptime field", + .owned = false, + .note = null, + }); + } +}; + +fn intFromFloatExact(T: type, value: anytype) ?T { + if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { + return null; + } + + if (std.math.isNan(value) or std.math.trunc(value) != value) { + return null; + } + + return @intFromFloat(value); +} + +fn canParseType(T: type) bool { + comptime return canParseTypeInner(T, &.{}, false); +} + +fn canParseTypeInner( + T: type, + /// Visited structs and unions, to avoid infinite recursion. + /// Tracking more types is unnecessary, and a little complex due to optional nesting. + visited: []const type, + parent_is_optional: bool, +) bool { + return switch (@typeInfo(T)) { + .bool, + .int, + .float, + .null, + .@"enum", + => true, + + .noreturn, + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + .comptime_int, + .comptime_float, + .enum_literal, + => false, + + .pointer => |pointer| switch (pointer.size) { + .one => canParseTypeInner(pointer.child, visited, parent_is_optional), + .slice => canParseTypeInner(pointer.child, visited, false), + .many, .c => false, + }, + + .optional => |optional| if (parent_is_optional) + false + else + canParseTypeInner(optional.child, visited, true), + + .array => |array| canParseTypeInner(array.child, visited, false), + .vector => |vector| canParseTypeInner(vector.child, visited, false), + + .@"struct" => |@"struct"| { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"struct".fields) |field| { + if (!field.is_comptime and !canParseTypeInner(field.type, new_visited, false)) { + return false; + } + } + return true; + }, + .@"union" => |@"union"| { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"union".fields) |field| { + if (field.type != void and !canParseTypeInner(field.type, new_visited, false)) { + return false; + } + } + return true; + }, + }; +} + +test "std.zon parse canParseType" { + try std.testing.expect(!comptime canParseType(void)); + try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); + try std.testing.expect(!comptime canParseType(struct { error{foo} })); + try std.testing.expect(!comptime canParseType(union(enum) { a: void, b: [*c]u8 })); + try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8))); + try std.testing.expect(!comptime canParseType(*?[*c]u8)); + try std.testing.expect(comptime canParseType(enum(u8) { _ })); + try std.testing.expect(comptime canParseType(union { foo: void })); + try std.testing.expect(comptime canParseType(union(enum) { foo: void })); + try std.testing.expect(!comptime canParseType(comptime_float)); + try std.testing.expect(!comptime canParseType(comptime_int)); + try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); + try std.testing.expect(comptime canParseType(?u8)); + try std.testing.expect(comptime canParseType(*?*u8)); + try std.testing.expect(comptime canParseType(?struct { + foo: ?struct { + ?union(enum) { + a: ?@Vector(0, ?*u8), + }, + ?struct { + f: ?[]?u8, + }, + }, + })); + try std.testing.expect(!comptime canParseType(??u8)); + try std.testing.expect(!comptime canParseType(?*?u8)); + try std.testing.expect(!comptime canParseType(*?*?*u8)); + try std.testing.expect(!comptime canParseType(struct { x: comptime_int = 2 })); + try std.testing.expect(!comptime canParseType(struct { x: comptime_float = 2 })); + try std.testing.expect(comptime canParseType(struct { comptime x: @TypeOf(.foo) = .foo })); + try std.testing.expect(!comptime canParseType(struct { comptime_int })); + const Recursive = struct { foo: ?*@This() }; + try std.testing.expect(comptime canParseType(Recursive)); + + // Make sure we validate nested optional before we early out due to already having seen + // a type recursion! + try std.testing.expect(!comptime canParseType(struct { + add_to_visited: ?u8, + retrieve_from_visited: ??u8, + })); +} + +test "std.zon requiresAllocator" { + try std.testing.expect(!requiresAllocator(u8)); + try std.testing.expect(!requiresAllocator(f32)); + try std.testing.expect(!requiresAllocator(enum { foo })); + try std.testing.expect(!requiresAllocator(struct { f32 })); + try std.testing.expect(!requiresAllocator(struct { x: f32 })); + try std.testing.expect(!requiresAllocator([0][]const u8)); + try std.testing.expect(!requiresAllocator([2]u8)); + try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(?f32)); + try std.testing.expect(!requiresAllocator(void)); + try std.testing.expect(!requiresAllocator(@TypeOf(null))); + try std.testing.expect(!requiresAllocator(@Vector(3, u8))); + try std.testing.expect(!requiresAllocator(@Vector(0, *const u8))); + + try std.testing.expect(requiresAllocator([]u8)); + try std.testing.expect(requiresAllocator(*struct { u8, u8 })); + try std.testing.expect(requiresAllocator([1][]const u8)); + try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(?[]u8)); + try std.testing.expect(requiresAllocator(@Vector(3, *const u8))); +} + +test "std.zon ast errors" { + const gpa = std.testing.allocator; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), + ); + try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status}); +} + +test "std.zon comments" { + const gpa = std.testing.allocator; + + try std.testing.expectEqual(@as(u8, 10), fromSlice(u8, gpa, + \\// comment + \\10 // comment + \\// comment + , null, .{})); + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, + \\//! comment + \\10 // comment + \\// comment + , &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected expression, found 'a document comment'\n", + "{}", + .{status}, + ); + } +} + +test "std.zon failure/oom formatting" { + const gpa = std.testing.allocator; + var failing_allocator = std.testing.FailingAllocator.init(gpa, .{ + .fail_index = 0, + .resize_fail_index = 0, + }); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.OutOfMemory, fromSlice( + []const u8, + failing_allocator.allocator(), + "\"foo\"", + &status, + .{}, + )); + try std.testing.expectFmt("", "{}", .{status}); +} + +test "std.zon fromSlice syntax error" { + try std.testing.expectError( + error.ParseZon, + fromSlice(u8, std.testing.allocator, ".{", null, .{}), + ); +} + +test "std.zon optional" { + const gpa = std.testing.allocator; + + // Basic usage + { + const none = try fromSlice(?u32, gpa, "null", null, .{}); + try std.testing.expect(none == null); + const some = try fromSlice(?u32, gpa, "1", null, .{}); + try std.testing.expect(some.? == 1); + } + + // Deep free + { + const none = try fromSlice(?[]const u8, gpa, "null", null, .{}); + try std.testing.expect(none == null); + const some = try fromSlice(?[]const u8, gpa, "\"foo\"", null, .{}); + defer free(gpa, some); + try std.testing.expectEqualStrings("foo", some.?); + } +} + +test "std.zon unions" { + const gpa = std.testing.allocator; + + // Unions + { + const Tagged = union(enum) { x: f32, @"y y": bool, z, @"z z" }; + const Untagged = union { x: f32, @"y y": bool, z: void, @"z z": void }; + + const tagged_x = try fromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{}); + try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x); + const tagged_y = try fromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{}); + try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y); + const tagged_z_shorthand = try fromSlice(Tagged, gpa, ".z", null, .{}); + try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand); + const tagged_zz_shorthand = try fromSlice(Tagged, gpa, ".@\"z z\"", null, .{}); + try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand); + + const untagged_x = try fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); + try std.testing.expect(untagged_x.x == 1.5); + const untagged_y = try fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); + try std.testing.expect(untagged_y.@"y y"); + } + + // Deep free + { + const Union = union(enum) { bar: []const u8, baz: bool }; + + const noalloc = try fromSlice(Union, gpa, ".{.baz = false}", null, .{}); + try std.testing.expectEqual(Union{ .baz = false }, noalloc); + + const alloc = try fromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{}); + defer free(gpa, alloc); + try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); + } + + // Unknown field + { + const Union = union { x: f32, y: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), + ); + try std.testing.expectFmt( + \\1:4: error: unexpected field 'z' + \\1:4: note: supported: 'x', 'y' + \\ + , + "{}", + .{status}, + ); + } + + // Explicit void field + { + const Union = union(enum) { x: void }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Union, gpa, ".{.x=1}", &status, .{}), + ); + try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status}); + } + + // Extra field + { + const Union = union { x: f32, y: bool }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); + } + + // No fields + { + const Union = union { x: f32, y: bool }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Union, gpa, ".{}", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); + } + + // Enum literals cannot coerce into untagged unions + { + const Union = union { x: void }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); + } + + // Unknown field for enum literal coercion + { + const Union = union(enum) { x: void }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".y", &status, .{})); + try std.testing.expectFmt( + \\1:2: error: unexpected field 'y' + \\1:2: note: supported: 'x' + \\ + , + "{}", + .{status}, + ); + } + + // Non void field for enum literal coercion + { + const Union = union(enum) { x: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); + } +} + +test "std.zon structs" { + const gpa = std.testing.allocator; + + // Structs (various sizes tested since they're parsed differently) + { + const Vec0 = struct {}; + const Vec1 = struct { x: f32 }; + const Vec2 = struct { x: f32, y: f32 }; + const Vec3 = struct { x: f32, y: f32, z: f32 }; + + const zero = try fromSlice(Vec0, gpa, ".{}", null, .{}); + try std.testing.expectEqual(Vec0{}, zero); + + const one = try fromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); + try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); + + const two = try fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{}); + try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two); + + const three = try fromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{}); + try std.testing.expectEqual(Vec3{ .x = 1.2, .y = 3.4, .z = 5.6 }, three); + } + + // Deep free (structs and arrays) + { + const Foo = struct { bar: []const u8, baz: []const []const u8 }; + + const parsed = try fromSlice( + Foo, + gpa, + ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", + null, + .{}, + ); + defer free(gpa, parsed); + try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); + } + + // Unknown field + { + const Vec2 = struct { x: f32, y: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + ); + try std.testing.expectFmt( + \\1:12: error: unexpected field 'z' + \\1:12: note: supported: 'x', 'y' + \\ + , + "{}", + .{status}, + ); + } + + // Duplicate field + { + const Vec2 = struct { x: f32, y: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5, .x=3.5}", &status, .{}), + ); + try std.testing.expectFmt( + \\1:4: error: duplicate struct field name + \\1:12: note: duplicate name here + \\ + , "{}", .{status}); + } + + // Ignore unknown fields + { + const Vec2 = struct { x: f32, y: f32 = 2.0 }; + const parsed = try fromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", null, .{ + .ignore_unknown_fields = true, + }); + try std.testing.expectEqual(Vec2{ .x = 1.0, .y = 2.0 }, parsed); + } + + // Unknown field when struct has no fields (regression test) + { + const Vec2 = struct {}; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + ); + try std.testing.expectFmt( + \\1:4: error: unexpected field 'x' + \\1:4: note: none expected + \\ + , "{}", .{status}); + } + + // Missing field + { + const Vec2 = struct { x: f32, y: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status}); + } + + // Default field + { + const Vec2 = struct { x: f32, y: f32 = 1.5 }; + const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); + } + + // Comptime field + { + const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; + const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); + } + + // Comptime field assignment + { + const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:18: error: cannot initialize comptime field + \\ + , "{}", .{status}); + } + + // Enum field (regression test, we were previously getting the field name in an + // incorrect way that broke for enum values) + { + const Vec0 = struct { x: enum { x } }; + const parsed = try fromSlice(Vec0, gpa, ".{ .x = .x }", null, .{}); + try std.testing.expectEqual(Vec0{ .x = .x }, parsed); + } + + // Enum field and struct field with @ + { + const Vec0 = struct { @"x x": enum { @"x x" } }; + const parsed = try fromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); + try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); + } + + // Type expressions are not allowed + { + // Structs + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice(struct {}, gpa, "Empty{}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:1: error: types are not available in ZON + \\1:1: note: replace the type with '.' + \\ + , "{}", .{status}); + } + + // Arrays + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice([3]u8, gpa, "[3]u8{1, 2, 3}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:1: error: types are not available in ZON + \\1:1: note: replace the type with '.' + \\ + , "{}", .{status}); + } + + // Slices + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice([]u8, gpa, "[]u8{1, 2, 3}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:1: error: types are not available in ZON + \\1:1: note: replace the type with '.' + \\ + , "{}", .{status}); + } + + // Tuples + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice( + struct { u8, u8, u8 }, + gpa, + "Tuple{1, 2, 3}", + &status, + .{}, + ); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:1: error: types are not available in ZON + \\1:1: note: replace the type with '.' + \\ + , "{}", .{status}); + } + + // Nested + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice(struct {}, gpa, ".{ .x = Tuple{1, 2, 3} }", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:9: error: types are not available in ZON + \\1:9: note: replace the type with '.' + \\ + , "{}", .{status}); + } + } +} + +test "std.zon tuples" { + const gpa = std.testing.allocator; + + // Structs (various sizes tested since they're parsed differently) + { + const Tuple0 = struct {}; + const Tuple1 = struct { f32 }; + const Tuple2 = struct { f32, bool }; + const Tuple3 = struct { f32, bool, u8 }; + + const zero = try fromSlice(Tuple0, gpa, ".{}", null, .{}); + try std.testing.expectEqual(Tuple0{}, zero); + + const one = try fromSlice(Tuple1, gpa, ".{1.2}", null, .{}); + try std.testing.expectEqual(Tuple1{1.2}, one); + + const two = try fromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); + try std.testing.expectEqual(Tuple2{ 1.2, true }, two); + + const three = try fromSlice(Tuple3, gpa, ".{1.2, false, 3}", null, .{}); + try std.testing.expectEqual(Tuple3{ 1.2, false, 3 }, three); + } + + // Deep free + { + const Tuple = struct { []const u8, []const u8 }; + const parsed = try fromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); + defer free(gpa, parsed); + try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); + } + + // Extra field + { + const Tuple = struct { f32, bool }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}), + ); + try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status}); + } + + // Extra field + { + const Tuple = struct { f32, bool }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Tuple, gpa, ".{0.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: missing tuple field with index 1\n", + "{}", + .{status}, + ); + } + + // Tuple with unexpected field names + { + const Tuple = struct { f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status}); + } + + // Struct with missing field names + { + const Struct = struct { foo: f32 }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Struct, gpa, ".{10.0}", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status}); + } + + // Comptime field + { + const Vec2 = struct { f32, comptime f32 = 1.5 }; + const parsed = try fromSlice(Vec2, gpa, ".{ 1.2 }", null, .{}); + try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed); + } + + // Comptime field assignment + { + const Vec2 = struct { f32, comptime f32 = 1.5 }; + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = fromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:9: error: cannot initialize comptime field + \\ + , "{}", .{status}); + } +} + +// Test sizes 0 to 3 since small sizes get parsed differently +test "std.zon arrays and slices" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/20881 + + const gpa = std.testing.allocator; + + // Literals + { + // Arrays + { + const zero = try fromSlice([0]u8, gpa, ".{}", null, .{}); + try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); + + const one = try fromSlice([1]u8, gpa, ".{'a'}", null, .{}); + try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); + + const two = try fromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{}); + try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two); + + const two_comma = try fromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{}); + try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma); + + const three = try fromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); + + const sentinel = try fromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + const expected_sentinel: [3:'z']u8 = .{ 'a', 'b', 'c' }; + try std.testing.expectEqualSlices(u8, &expected_sentinel, &sentinel); + } + + // Slice literals + { + const zero = try fromSlice([]const u8, gpa, ".{}", null, .{}); + defer free(gpa, zero); + try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); + + const one = try fromSlice([]u8, gpa, ".{'a'}", null, .{}); + defer free(gpa, one); + try std.testing.expectEqualSlices(u8, &.{'a'}, one); + + const two = try fromSlice([]const u8, gpa, ".{'a', 'b'}", null, .{}); + defer free(gpa, two); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two); + + const two_comma = try fromSlice([]const u8, gpa, ".{'a', 'b',}", null, .{}); + defer free(gpa, two_comma); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma); + + const three = try fromSlice([]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + defer free(gpa, three); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three); + + const sentinel = try fromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + defer free(gpa, sentinel); + const expected_sentinel: [:'z']const u8 = &.{ 'a', 'b', 'c' }; + try std.testing.expectEqualSlices(u8, expected_sentinel, sentinel); + } + } + + // Deep free + { + // Arrays + { + const parsed = try fromSlice([1][]const u8, gpa, ".{\"abc\"}", null, .{}); + defer free(gpa, parsed); + const expected: [1][]const u8 = .{"abc"}; + try std.testing.expectEqualDeep(expected, parsed); + } + + // Slice literals + { + const parsed = try fromSlice([]const []const u8, gpa, ".{\"abc\"}", null, .{}); + defer free(gpa, parsed); + const expected: []const []const u8 = &.{"abc"}; + try std.testing.expectEqualDeep(expected, parsed); + } + } + + // Sentinels and alignment + { + // Arrays + { + const sentinel = try fromSlice([1:2]u8, gpa, ".{1}", null, .{}); + try std.testing.expectEqual(@as(usize, 1), sentinel.len); + try std.testing.expectEqual(@as(u8, 1), sentinel[0]); + try std.testing.expectEqual(@as(u8, 2), sentinel[1]); + } + + // Slice literals + { + const sentinel = try fromSlice([:2]align(4) u8, gpa, ".{1}", null, .{}); + defer free(gpa, sentinel); + try std.testing.expectEqual(@as(usize, 1), sentinel.len); + try std.testing.expectEqual(@as(u8, 1), sentinel[0]); + try std.testing.expectEqual(@as(u8, 2), sentinel[1]); + } + } + + // Expect 0 find 3 + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:3: error: index 0 outside of array of length 0\n", + "{}", + .{status}, + ); + } + + // Expect 1 find 2 + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:8: error: index 1 outside of array of length 1\n", + "{}", + .{status}, + ); + } + + // Expect 2 find 1 + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([2]u8, gpa, ".{'a'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 2 array elements; found 1\n", + "{}", + .{status}, + ); + } + + // Expect 3 find 0 + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([3]u8, gpa, ".{}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 3 array elements; found 0\n", + "{}", + .{status}, + ); + } + + // Wrong inner type + { + // Array + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); + } + + // Slice + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); + } + } + + // Complete wrong type + { + // Array + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([3]u8, gpa, "'a'", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + // Slice + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]u8, gpa, "'a'", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Address of is not allowed (indirection for slices in ZON is implicit) + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:3: error: pointers are not available in ZON\n", + "{}", + .{status}, + ); + } +} + +test "std.zon string literal" { + const gpa = std.testing.allocator; + + // Basic string literal + { + const parsed = try fromSlice([]const u8, gpa, "\"abc\"", null, .{}); + defer free(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed); + } + + // String literal with escape characters + { + const parsed = try fromSlice([]const u8, gpa, "\"ab\\nc\"", null, .{}); + defer free(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed); + } + + // String literal with embedded null + { + const parsed = try fromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{}); + defer free(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); + } + + // Passing string literal to a mutable slice + { + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]u8, gpa, "\"abcd\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]u8, gpa, "\\\\abcd", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Passing string literal to a array + { + { + var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); + defer ast.deinit(gpa); + var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); + defer zoir.deinit(gpa); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Zero terminated slices + { + { + const parsed: [:0]const u8 = try fromSlice( + [:0]const u8, + gpa, + "\"abc\"", + null, + .{}, + ); + defer free(gpa, parsed); + try std.testing.expectEqualStrings("abc", parsed); + try std.testing.expectEqual(@as(u8, 0), parsed[3]); + } + + { + const parsed: [:0]const u8 = try fromSlice( + [:0]const u8, + gpa, + "\\\\abc", + null, + .{}, + ); + defer free(gpa, parsed); + try std.testing.expectEqualStrings("abc", parsed); + try std.testing.expectEqual(@as(u8, 0), parsed[3]); + } + } + + // Other value terminated slices + { + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Expecting string literal, getting something else + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected string\n", "{}", .{status}); + } + + // Expecting string literal, getting an incompatible tuple + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]const u8, gpa, ".{false}", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{}", .{status}); + } + + // Invalid string literal + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{}", .{status}); + } + + // Slice wrong child type + { + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]const i8, gpa, "\"a\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]const i8, gpa, "\\\\a", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Bad alignment + { + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); + } + } + + // Multi line strings + inline for (.{ []const u8, [:0]const u8 }) |String| { + // Nested + { + const S = struct { + message: String, + message2: String, + message3: String, + }; + const parsed = try fromSlice(S, gpa, + \\.{ + \\ .message = + \\ \\hello, world! + \\ + \\ \\this is a multiline string! + \\ \\ + \\ \\... + \\ + \\ , + \\ .message2 = + \\ \\this too...sort of. + \\ , + \\ .message3 = + \\ \\ + \\ \\and this. + \\} + , null, .{}); + defer free(gpa, parsed); + try std.testing.expectEqualStrings( + "hello, world!\nthis is a multiline string!\n\n...", + parsed.message, + ); + try std.testing.expectEqualStrings("this too...sort of.", parsed.message2); + try std.testing.expectEqualStrings("\nand this.", parsed.message3); + } + } +} + +test "std.zon enum literals" { + const gpa = std.testing.allocator; + + const Enum = enum { + foo, + bar, + baz, + @"ab\nc", + }; + + // Tags that exist + try std.testing.expectEqual(Enum.foo, try fromSlice(Enum, gpa, ".foo", null, .{})); + try std.testing.expectEqual(Enum.bar, try fromSlice(Enum, gpa, ".bar", null, .{})); + try std.testing.expectEqual(Enum.baz, try fromSlice(Enum, gpa, ".baz", null, .{})); + try std.testing.expectEqual( + Enum.@"ab\nc", + try fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), + ); + + // Bad tag + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Enum, gpa, ".qux", &status, .{}), + ); + try std.testing.expectFmt( + \\1:2: error: unexpected enum literal 'qux' + \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\ + , + "{}", + .{status}, + ); + } + + // Bad tag that's too long for parser + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), + ); + try std.testing.expectFmt( + \\1:2: error: unexpected enum literal 'foobarbaz' + \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\ + , + "{}", + .{status}, + ); + } + + // Bad type + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Enum, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status}); + } + + // Test embedded nulls in an identifier + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: identifier cannot contain null bytes\n", + "{}", + .{status}, + ); + } +} + +test "std.zon parse bool" { + const gpa = std.testing.allocator; + + // Correct floats + try std.testing.expectEqual(true, try fromSlice(bool, gpa, "true", null, .{})); + try std.testing.expectEqual(false, try fromSlice(bool, gpa, "false", null, .{})); + + // Errors + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(bool, gpa, " foo", &status, .{}), + ); + try std.testing.expectFmt( + \\1:2: error: invalid expression + \\1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' + \\1:2: note: precede identifier with '.' for an enum literal + \\ + , "{}", .{status}); + } + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(bool, gpa, "123", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{}", .{status}); + } +} + +test "std.zon intFromFloatExact" { + // Valid conversions + try std.testing.expectEqual(@as(u8, 10), intFromFloatExact(u8, @as(f32, 10.0)).?); + try std.testing.expectEqual(@as(i8, -123), intFromFloatExact(i8, @as(f64, @as(f64, -123.0))).?); + try std.testing.expectEqual(@as(i16, 45), intFromFloatExact(i16, @as(f128, @as(f128, 45.0))).?); + + // Out of range + try std.testing.expectEqual(@as(?u4, null), intFromFloatExact(u4, @as(f32, 16.0))); + try std.testing.expectEqual(@as(?i4, null), intFromFloatExact(i4, @as(f64, -17.0))); + try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f128, -2.0))); + + // Not a whole number + try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f32, 0.5))); + try std.testing.expectEqual(@as(?i8, null), intFromFloatExact(i8, @as(f64, 0.01))); + + // Infinity and NaN + try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.inf(f32))); + try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, -std.math.inf(f32))); + try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.nan(f32))); +} + +test "std.zon parse int" { + const gpa = std.testing.allocator; + + // Test various numbers and types + try std.testing.expectEqual(@as(u8, 10), try fromSlice(u8, gpa, "10", null, .{})); + try std.testing.expectEqual(@as(i16, 24), try fromSlice(i16, gpa, "24", null, .{})); + try std.testing.expectEqual(@as(i14, -4), try fromSlice(i14, gpa, "-4", null, .{})); + try std.testing.expectEqual(@as(i32, -123), try fromSlice(i32, gpa, "-123", null, .{})); + + // Test limits + try std.testing.expectEqual(@as(i8, 127), try fromSlice(i8, gpa, "127", null, .{})); + try std.testing.expectEqual(@as(i8, -128), try fromSlice(i8, gpa, "-128", null, .{})); + + // Test characters + try std.testing.expectEqual(@as(u8, 'a'), try fromSlice(u8, gpa, "'a'", null, .{})); + try std.testing.expectEqual(@as(u8, 'z'), try fromSlice(u8, gpa, "'z'", null, .{})); + + // Test big integers + try std.testing.expectEqual( + @as(u65, 36893488147419103231), + try fromSlice(u65, gpa, "36893488147419103231", null, .{}), + ); + try std.testing.expectEqual( + @as(u65, 36893488147419103231), + try fromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), + ); + + // Test big integer limits + try std.testing.expectEqual( + @as(i66, 36893488147419103231), + try fromSlice(i66, gpa, "36893488147419103231", null, .{}), + ); + try std.testing.expectEqual( + @as(i66, -36893488147419103232), + try fromSlice(i66, gpa, "-36893488147419103232", null, .{}), + ); + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice( + i66, + gpa, + "36893488147419103232", + &status, + .{}, + )); + try std.testing.expectFmt( + "1:1: error: type 'i66' cannot represent value\n", + "{}", + .{status}, + ); + } + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice( + i66, + gpa, + "-36893488147419103233", + &status, + .{}, + )); + try std.testing.expectFmt( + "1:1: error: type 'i66' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Test parsing whole number floats as integers + try std.testing.expectEqual(@as(i8, -1), try fromSlice(i8, gpa, "-1.0", null, .{})); + try std.testing.expectEqual(@as(i8, 123), try fromSlice(i8, gpa, "123.0", null, .{})); + + // Test non-decimal integers + try std.testing.expectEqual(@as(i16, 0xff), try fromSlice(i16, gpa, "0xff", null, .{})); + try std.testing.expectEqual(@as(i16, -0xff), try fromSlice(i16, gpa, "-0xff", null, .{})); + try std.testing.expectEqual(@as(i16, 0o77), try fromSlice(i16, gpa, "0o77", null, .{})); + try std.testing.expectEqual(@as(i16, -0o77), try fromSlice(i16, gpa, "-0o77", null, .{})); + try std.testing.expectEqual(@as(i16, 0b11), try fromSlice(i16, gpa, "0b11", null, .{})); + try std.testing.expectEqual(@as(i16, -0b11), try fromSlice(i16, gpa, "-0b11", null, .{})); + + // Test non-decimal big integers + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( + u65, + gpa, + "0x1ffffffffffffffff", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "0x1ffffffffffffffff", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "-0x1ffffffffffffffff", + null, + .{}, + )); + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( + u65, + gpa, + "0o3777777777777777777777", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "0o3777777777777777777777", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "-0o3777777777777777777777", + null, + .{}, + )); + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( + u65, + gpa, + "0b11111111111111111111111111111111111111111111111111111111111111111", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "0b11111111111111111111111111111111111111111111111111111111111111111", + null, + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( + i66, + gpa, + "-0b11111111111111111111111111111111111111111111111111111111111111111", + null, + .{}, + )); + + // Number with invalid character in the middle + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "32a32", &status, .{})); + try std.testing.expectFmt( + "1:3: error: invalid digit 'a' for decimal base\n", + "{}", + .{status}, + ); + } + + // Failing to parse as int + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "true", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{}", .{status}); + } + + // Failing because an int is out of range + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "256", &status, .{})); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Failing because a negative int is out of range + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-129", &status, .{})); + try std.testing.expectFmt( + "1:1: error: type 'i8' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Failing because an unsigned int is negative + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1", &status, .{})); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Failing because a float is non-whole + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "1.5", &status, .{})); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Failing because a float is negative + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1.0", &status, .{})); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); + } + + // Negative integer zero + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-0", &status, .{})); + try std.testing.expectFmt( + \\1:2: error: integer literal '-0' is ambiguous + \\1:2: note: use '0' for an integer zero + \\1:2: note: use '-0.0' for a floating-point signed zero + \\ + , "{}", .{status}); + } + + // Negative integer zero casted to float + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-0", &status, .{})); + try std.testing.expectFmt( + \\1:2: error: integer literal '-0' is ambiguous + \\1:2: note: use '0' for an integer zero + \\1:2: note: use '-0.0' for a floating-point signed zero + \\ + , "{}", .{status}); + } + + // Negative float 0 is allowed + try std.testing.expect( + std.math.isNegativeZero(try fromSlice(f32, gpa, "-0.0", null, .{})), + ); + try std.testing.expect(std.math.isPositiveZero(try fromSlice(f32, gpa, "0.0", null, .{}))); + + // Double negation is not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "--2", &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(f32, gpa, "--2.0", &status, .{}), + ); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } + + // Invalid int literal + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0xg", &status, .{})); + try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base\n", "{}", .{status}); + } + + // Notes on invalid int literal + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0123", &status, .{})); + try std.testing.expectFmt( + \\1:1: error: number '0123' has leading zero + \\1:1: note: use '0o' prefix for octal literals + \\ + , "{}", .{status}); + } +} + +test "std.zon negative char" { + const gpa = std.testing.allocator; + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-'a'", &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i16, gpa, "-'a'", &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } +} + +test "std.zon parse float" { + const gpa = std.testing.allocator; + + // Test decimals + try std.testing.expectEqual(@as(f16, 0.5), try fromSlice(f16, gpa, "0.5", null, .{})); + try std.testing.expectEqual( + @as(f32, 123.456), + try fromSlice(f32, gpa, "123.456", null, .{}), + ); + try std.testing.expectEqual( + @as(f64, -123.456), + try fromSlice(f64, gpa, "-123.456", null, .{}), + ); + try std.testing.expectEqual(@as(f128, 42.5), try fromSlice(f128, gpa, "42.5", null, .{})); + + // Test whole numbers with and without decimals + try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5.0", null, .{})); + try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5", null, .{})); + try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102.0", null, .{})); + try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102", null, .{})); + + // Test characters and negated characters + try std.testing.expectEqual(@as(f32, 'a'), try fromSlice(f32, gpa, "'a'", null, .{})); + try std.testing.expectEqual(@as(f32, 'z'), try fromSlice(f32, gpa, "'z'", null, .{})); + + // Test big integers + try std.testing.expectEqual( + @as(f32, 36893488147419103231), + try fromSlice(f32, gpa, "36893488147419103231", null, .{}), + ); + try std.testing.expectEqual( + @as(f32, -36893488147419103231), + try fromSlice(f32, gpa, "-36893488147419103231", null, .{}), + ); + try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice( + f128, + gpa, + "0x1ffffffffffffffff", + null, + .{}, + )); + try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice( + f32, + gpa, + "0x1ffffffffffffffff", + null, + .{}, + )); + + // Exponents, underscores + try std.testing.expectEqual( + @as(f32, 123.0E+77), + try fromSlice(f32, gpa, "12_3.0E+77", null, .{}), + ); + + // Hexadecimal + try std.testing.expectEqual( + @as(f32, 0x103.70p-5), + try fromSlice(f32, gpa, "0x103.70p-5", null, .{}), + ); + try std.testing.expectEqual( + @as(f32, -0x103.70), + try fromSlice(f32, gpa, "-0x103.70", null, .{}), + ); + try std.testing.expectEqual( + @as(f32, 0x1234_5678.9ABC_CDEFp-10), + try fromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), + ); + + // inf, nan + try std.testing.expect(std.math.isPositiveInf(try fromSlice(f32, gpa, "inf", null, .{}))); + try std.testing.expect(std.math.isNegativeInf(try fromSlice(f32, gpa, "-inf", null, .{}))); + try std.testing.expect(std.math.isNan(try fromSlice(f32, gpa, "nan", null, .{}))); + + // Negative nan not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-nan", &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } + + // nan as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); + } + + // nan as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); + } + + // inf as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "inf", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); + } + + // -inf as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-inf", &status, .{})); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); + } + + // Bad identifier as float + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "foo", &status, .{})); + try std.testing.expectFmt( + \\1:1: error: invalid expression + \\1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' + \\1:1: note: precede identifier with '.' for an enum literal + \\ + , "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-foo", &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); + } + + // Non float as float + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(f32, gpa, "\"foo\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status}); + } +} + +test "std.zon free on error" { + // Test freeing partially allocated structs + { + const Struct = struct { + x: []const u8, + y: []const u8, + z: bool, + }; + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, + \\.{ + \\ .x = "hello", + \\ .y = "world", + \\ .z = "fail", + \\} + , null, .{})); + } + + // Test freeing partially allocated tuples + { + const Struct = struct { + []const u8, + []const u8, + bool, + }; + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, + \\.{ + \\ "hello", + \\ "world", + \\ "fail", + \\} + , null, .{})); + } + + // Test freeing structs with missing fields + { + const Struct = struct { + x: []const u8, + y: bool, + }; + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, + \\.{ + \\ .x = "hello", + \\} + , null, .{})); + } + + // Test freeing partially allocated arrays + { + try std.testing.expectError(error.ParseZon, fromSlice( + [3][]const u8, + std.testing.allocator, + \\.{ + \\ "hello", + \\ false, + \\ false, + \\} + , + null, + .{}, + )); + } + + // Test freeing partially allocated slices + { + try std.testing.expectError(error.ParseZon, fromSlice( + [][]const u8, + std.testing.allocator, + \\.{ + \\ "hello", + \\ "world", + \\ false, + \\} + , + null, + .{}, + )); + } + + // We can parse types that can't be freed, as long as they contain no allocations, e.g. untagged + // unions. + try std.testing.expectEqual( + @as(f32, 1.5), + (try fromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", null, .{})).x, + ); + + // We can also parse types that can't be freed if it's impossible for an error to occur after + // the allocation, as is the case here. + { + const result = try fromSlice( + union { x: []const u8 }, + std.testing.allocator, + ".{ .x = \"foo\" }", + null, + .{}, + ); + defer free(std.testing.allocator, result.x); + try std.testing.expectEqualStrings("foo", result.x); + } + + // However, if it's possible we could get an error requiring we free the value, but the value + // cannot be freed (e.g. untagged unions) then we need to turn off `free_on_error` for it to + // compile. + { + const S = struct { + union { x: []const u8 }, + bool, + }; + const result = try fromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, true }", + null, + .{ .free_on_error = false }, + ); + defer free(std.testing.allocator, result[0].x); + try std.testing.expectEqualStrings("foo", result[0].x); + try std.testing.expect(result[1]); + } + + // Again but for structs. + { + const S = struct { + a: union { x: []const u8 }, + b: bool, + }; + const result = try fromSlice( + S, + std.testing.allocator, + ".{ .a = .{ .x = \"foo\" }, .b = true }", + null, + .{ + .free_on_error = false, + }, + ); + defer free(std.testing.allocator, result.a.x); + try std.testing.expectEqualStrings("foo", result.a.x); + try std.testing.expect(result.b); + } + + // Again but for arrays. + { + const S = [2]union { x: []const u8 }; + const result = try fromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", + null, + .{ + .free_on_error = false, + }, + ); + defer free(std.testing.allocator, result[0].x); + defer free(std.testing.allocator, result[1].x); + try std.testing.expectEqualStrings("foo", result[0].x); + try std.testing.expectEqualStrings("bar", result[1].x); + } + + // Again but for slices. + { + const S = []union { x: []const u8 }; + const result = try fromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", + null, + .{ + .free_on_error = false, + }, + ); + defer std.testing.allocator.free(result); + defer free(std.testing.allocator, result[0].x); + defer free(std.testing.allocator, result[1].x); + try std.testing.expectEqualStrings("foo", result[0].x); + try std.testing.expectEqualStrings("bar", result[1].x); + } +} + +test "std.zon vector" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330 + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15329 + + const gpa = std.testing.allocator; + + // Passing cases + try std.testing.expectEqual( + @Vector(0, bool){}, + try fromSlice(@Vector(0, bool), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, bool){ true, false, true }, + try fromSlice(@Vector(3, bool), gpa, ".{true, false, true}", null, .{}), + ); + + try std.testing.expectEqual( + @Vector(0, f32){}, + try fromSlice(@Vector(0, f32), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, f32){ 1.5, 2.5, 3.5 }, + try fromSlice(@Vector(3, f32), gpa, ".{1.5, 2.5, 3.5}", null, .{}), + ); + + try std.testing.expectEqual( + @Vector(0, u8){}, + try fromSlice(@Vector(0, u8), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, u8){ 2, 4, 6 }, + try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}), + ); + + { + try std.testing.expectEqual( + @Vector(0, *const u8){}, + try fromSlice(@Vector(0, *const u8), gpa, ".{}", null, .{}), + ); + const pointers = try fromSlice(@Vector(3, *const u8), gpa, ".{2, 4, 6}", null, .{}); + defer free(gpa, pointers); + try std.testing.expectEqualDeep(@Vector(3, *const u8){ &2, &4, &6 }, pointers); + } + + { + try std.testing.expectEqual( + @Vector(0, ?*const u8){}, + try fromSlice(@Vector(0, ?*const u8), gpa, ".{}", null, .{}), + ); + const pointers = try fromSlice(@Vector(3, ?*const u8), gpa, ".{2, null, 6}", null, .{}); + defer free(gpa, pointers); + try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers); + } + + // Too few fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(2, f32), gpa, ".{0.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 2 vector elements; found 1\n", + "{}", + .{status}, + ); + } + + // Too many fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(2, f32), gpa, ".{0.5, 1.5, 2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 2 vector elements; found 3\n", + "{}", + .{status}, + ); + } + + // Wrong type fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, f32), gpa, ".{0.5, true, 2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:8: error: expected type 'f32'\n", + "{}", + .{status}, + ); + } + + // Wrong type + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, u8), gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{}", .{status}); + } + + // Elements should get freed on error + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, *u8), gpa, ".{1, true, 3}", &status, .{}), + ); + try std.testing.expectFmt("1:6: error: expected type 'u8'\n", "{}", .{status}); + } +} + +test "std.zon add pointers" { + const gpa = std.testing.allocator; + + // Primitive with varying levels of pointers + { + const result = try fromSlice(*u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*); + } + + { + const result = try fromSlice(**u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*.*); + } + + { + const result = try fromSlice(***u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*.*.*); + } + + // Primitive optional with varying levels of pointers + { + const some = try fromSlice(?*u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.?.*); + + const none = try fromSlice(?*u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none); + } + + { + const some = try fromSlice(*?u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.?); + + const none = try fromSlice(*?u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*); + } + + { + const some = try fromSlice(?**u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.?.*.*); + + const none = try fromSlice(?**u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none); + } + + { + const some = try fromSlice(*?*u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.?.*); + + const none = try fromSlice(*?*u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*); + } + + { + const some = try fromSlice(**?u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.*.?); + + const none = try fromSlice(**?u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*.*); + } + + // Pointer to an array + { + const result = try fromSlice(*[3]u8, gpa, ".{ 1, 2, 3 }", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*); + } + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const expected: Outer = .{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + const found = try fromSlice(?*Outer, gpa, + \\.{ + \\ .f1 = .{ + \\ .f1 = null, + \\ .f2 = "foo", + \\ }, + \\ .f2 = null, + \\} + , null, .{}); + defer free(gpa, found); + + try std.testing.expectEqualDeep(expected, found.?.*); + } + + // Test that optional types are flattened correctly in errors + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?u8'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const f32, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?f32'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const @Vector(3, u8), gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?@Vector(3, u8)'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const bool, gpa, "10", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?bool'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const struct { a: i32 }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional struct\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const struct { i32 }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional tuple\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const union { x: void }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional union\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const [3]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(?[3]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const []u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(?[]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const []const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional string\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const enum { foo }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional enum literal\n", "{}", .{status}); + } +} diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig new file mode 100644 index 000000000000..a3f2b9cc003c --- /dev/null +++ b/lib/std/zon/stringify.zig @@ -0,0 +1,2306 @@ +//! ZON can be serialized with `serialize`. +//! +//! The following functions are provided for serializing recursive types: +//! * `serializeMaxDepth` +//! * `serializeArbitraryDepth` +//! +//! For additional control over serialization, see `Serializer`. +//! +//! The following types and any types that contain them may not be serialized: +//! * `type` +//! * `void`, except as a union payload +//! * `noreturn` +//! * Error sets/error unions +//! * Untagged unions +//! * Many-pointers or C-pointers +//! * Opaque types, including `anyopaque` +//! * Async frame types, including `anyframe` and `anyframe->T` +//! * Functions +//! +//! All other types are valid. Unsupported types will fail to serialize at compile time. Pointers +//! are followed. + +const std = @import("std"); +const assert = std.debug.assert; + +/// Options for `serialize`. +pub const SerializeOptions = struct { + /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. + whitespace: bool = true, + /// Determines when to emit Unicode code point literals as opposed to integer literals. + emit_codepoint_literals: EmitCodepointLiterals = .never, + /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. + /// Otherwise they are serialized as string literals. + emit_strings_as_containers: bool = false, + /// If false, struct fields are not written if they are equal to their default value. Comparison + /// is done by `std.meta.eql`. + emit_default_optional_fields: bool = true, +}; + +/// Serialize the given value as ZON. +/// +/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type. +pub fn serialize( + val: anytype, + options: SerializeOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.value(val, .{ + .emit_codepoint_literals = options.emit_codepoint_literals, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }); +} + +/// Like `serialize`, but recursive types are allowed. +/// +/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. Every nested value adds one to a +/// value's depth. +pub fn serializeMaxDepth( + val: anytype, + options: SerializeOptions, + writer: anytype, + depth: usize, +) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.valueMaxDepth(val, .{ + .emit_codepoint_literals = options.emit_codepoint_literals, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }, depth); +} + +/// Like `serialize`, but recursive types are allowed. +/// +/// It is the caller's responsibility to ensure that `val` does not contain cycles. +pub fn serializeArbitraryDepth( + val: anytype, + options: SerializeOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var sz = serializer(writer, .{ + .whitespace = options.whitespace, + }); + try sz.valueArbitraryDepth(val, .{ + .emit_codepoint_literals = options.emit_codepoint_literals, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }); +} + +fn typeIsRecursive(comptime T: type) bool { + return comptime typeIsRecursiveImpl(T, &.{}); +} + +fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool { + for (prev_visited) |V| { + if (V == T) return true; + } + const visited = prev_visited ++ .{T}; + + return switch (@typeInfo(T)) { + .pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited), + .optional => |optional| typeIsRecursiveImpl(optional.child, visited), + .array => |array| typeIsRecursiveImpl(array.child, visited), + .vector => |vector| typeIsRecursiveImpl(vector.child, visited), + .@"struct" => |@"struct"| for (@"struct".fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) break true; + } else false, + .@"union" => |@"union"| inline for (@"union".fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) break true; + } else false, + else => false, + }; +} + +fn canSerializeType(T: type) bool { + comptime return canSerializeTypeInner(T, &.{}, false); +} + +fn canSerializeTypeInner( + T: type, + /// Visited structs and unions, to avoid infinite recursion. + /// Tracking more types is unnecessary, and a little complex due to optional nesting. + visited: []const type, + parent_is_optional: bool, +) bool { + return switch (@typeInfo(T)) { + .bool, + .int, + .float, + .comptime_float, + .comptime_int, + .null, + .enum_literal, + => true, + + .noreturn, + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + => false, + + .@"enum" => |@"enum"| @"enum".is_exhaustive, + + .pointer => |pointer| switch (pointer.size) { + .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), + .slice => canSerializeTypeInner(pointer.child, visited, false), + .many, .c => false, + }, + + .optional => |optional| if (parent_is_optional) + false + else + canSerializeTypeInner(optional.child, visited, true), + + .array => |array| canSerializeTypeInner(array.child, visited, false), + .vector => |vector| canSerializeTypeInner(vector.child, visited, false), + + .@"struct" => |@"struct"| { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"struct".fields) |field| { + if (!canSerializeTypeInner(field.type, new_visited, false)) return false; + } + return true; + }, + .@"union" => |@"union"| { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + if (@"union".tag_type == null) return false; + for (@"union".fields) |field| { + if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { + return false; + } + } + return true; + }, + }; +} + +fn isNestedOptional(T: type) bool { + comptime switch (@typeInfo(T)) { + .optional => |optional| return isNestedOptionalInner(optional.child), + else => return false, + }; +} + +fn isNestedOptionalInner(T: type) bool { + switch (@typeInfo(T)) { + .pointer => |pointer| { + if (pointer.size == .one) { + return isNestedOptionalInner(pointer.child); + } else { + return false; + } + }, + .optional => return true, + else => return false, + } +} + +test "std.zon stringify canSerializeType" { + try std.testing.expect(!comptime canSerializeType(void)); + try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); + try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); + try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); + try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); + try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); + try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); + try std.testing.expect(!comptime canSerializeType(union { foo: void })); + try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); + try std.testing.expect(comptime canSerializeType(comptime_float)); + try std.testing.expect(comptime canSerializeType(comptime_int)); + try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); + try std.testing.expect(comptime canSerializeType(?u8)); + try std.testing.expect(comptime canSerializeType(*?*u8)); + try std.testing.expect(comptime canSerializeType(?struct { + foo: ?struct { + ?union(enum) { + a: ?@Vector(0, ?*u8), + }, + ?struct { + f: ?[]?u8, + }, + }, + })); + try std.testing.expect(!comptime canSerializeType(??u8)); + try std.testing.expect(!comptime canSerializeType(?*?u8)); + try std.testing.expect(!comptime canSerializeType(*?*?*u8)); + try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); + try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); + try std.testing.expect(comptime canSerializeType(struct { comptime_int })); + try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); + const Recursive = struct { foo: ?*@This() }; + try std.testing.expect(comptime canSerializeType(Recursive)); + + // Make sure we validate nested optional before we early out due to already having seen + // a type recursion! + try std.testing.expect(!comptime canSerializeType(struct { + add_to_visited: ?u8, + retrieve_from_visited: ??u8, + })); +} + +test "std.zon typeIsRecursive" { + try std.testing.expect(!typeIsRecursive(bool)); + try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); + try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); + try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); + try std.testing.expect(typeIsRecursive(struct { + a: struct { + const A = @This(); + b: struct { + c: *struct { + a: ?A, + }, + }, + }, + })); + try std.testing.expect(typeIsRecursive(struct { + a: [3]*@This(), + })); + try std.testing.expect(typeIsRecursive(struct { + a: union { a: i32, b: *@This() }, + })); +} + +fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { + if (depth == 0) return error.ExceededMaxDepth; + const child_depth = depth - 1; + + switch (@typeInfo(@TypeOf(val))) { + .pointer => |pointer| switch (pointer.size) { + .one => try checkValueDepth(val.*, child_depth), + .slice => for (val) |item| { + try checkValueDepth(item, child_depth); + }, + .c, .many => {}, + }, + .array => for (val) |item| { + try checkValueDepth(item, child_depth); + }, + .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { + try checkValueDepth(@field(val, field_info.name), child_depth); + }, + .@"union" => |@"union"| if (@"union".tag_type == null) { + return; + } else switch (val) { + inline else => |payload| { + return checkValueDepth(payload, child_depth); + }, + }, + .optional => if (val) |inner| try checkValueDepth(inner, child_depth), + else => {}, + } +} + +fn expectValueDepthEquals(expected: usize, value: anytype) !void { + try checkValueDepth(value, expected); + try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1)); +} + +test "std.zon checkValueDepth" { + try expectValueDepthEquals(1, 10); + try expectValueDepthEquals(2, .{ .x = 1, .y = 2 }); + try expectValueDepthEquals(2, .{ 1, 2 }); + try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } }); + try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 }); + try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } }); + try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 }); + try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 }); + try expectValueDepthEquals(2, @as(?u32, 1)); + try expectValueDepthEquals(1, @as(?u32, null)); + try expectValueDepthEquals(1, null); + try expectValueDepthEquals(2, &1); + try expectValueDepthEquals(3, &@as(?u32, 1)); + + const Union = union(enum) { + x: u32, + y: struct { x: u32 }, + }; + try expectValueDepthEquals(2, Union{ .x = 1 }); + try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } }); + + const Recurse = struct { r: ?*const @This() }; + try expectValueDepthEquals(2, Recurse{ .r = null }); + try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } }); + try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } }); + + try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 })); + try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); +} + +/// Options for `Serializer`. +pub const SerializerOptions = struct { + /// If false, only syntactically necessary whitespace is emitted. + whitespace: bool = true, +}; + +/// Determines when to emit Unicode code point literals as opposed to integer literals. +pub const EmitCodepointLiterals = enum { + /// Never emit Unicode code point literals. + never, + /// Emit Unicode code point literals for any `u8` in the printable ASCII range. + printable_ascii, + /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer + /// whose value is a valid non-surrogate code point. + always, + + /// If the value should be emitted as a Unicode codepoint, return it as a u21. + fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { + // Rule out incompatible integer types + switch (@typeInfo(@TypeOf(val))) { + .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { + return null; + }, + .comptime_int => {}, + else => comptime unreachable, + } + + // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted + // to a u21 if it should. + switch (self) { + .always => { + const c = std.math.cast(u21, val) orelse return null; + if (!std.unicode.utf8ValidCodepoint(c)) return null; + return c; + }, + .printable_ascii => { + const c = std.math.cast(u8, val) orelse return null; + if (!std.ascii.isPrint(c)) return null; + return c; + }, + .never => { + return null; + }, + } + } +}; + +/// Options for serialization of an individual value. +/// +/// See `SerializeOptions` for more information on these options. +pub const ValueOptions = struct { + emit_codepoint_literals: EmitCodepointLiterals = .never, + emit_strings_as_containers: bool = false, + emit_default_optional_fields: bool = true, +}; + +/// Options for manual serialization of container types. +pub const SerializeContainerOptions = struct { + /// The whitespace style that should be used for this container. Ignored if whitespace is off. + whitespace_style: union(enum) { + /// If true, wrap every field. If false do not. + wrap: bool, + /// Automatically decide whether to wrap or not based on the number of fields. Following + /// the standard rule of thumb, containers with more than two fields are wrapped. + fields: usize, + } = .{ .wrap = true }, + + fn shouldWrap(self: SerializeContainerOptions) bool { + return switch (self.whitespace_style) { + .wrap => |wrap| wrap, + .fields => |fields| fields > 2, + }; + } +}; + +/// Lower level control over serialization, you can create a new instance with `serializer`. +/// +/// Useful when you want control over which fields are serialized, how they're represented, +/// or want to write a ZON object that does not exist in memory. +/// +/// You can serialize values with `value`. To serialize recursive types, the following are provided: +/// * `valueMaxDepth` +/// * `valueArbitraryDepth` +/// +/// You can also serialize values using specific notations: +/// * `int` +/// * `float` +/// * `codePoint` +/// * `tuple` +/// * `tupleMaxDepth` +/// * `tupleArbitraryDepth` +/// * `string` +/// * `multilineString` +/// +/// For manual serialization of containers, see: +/// * `startStruct` +/// * `startTuple` +/// +/// # Example +/// ```zig +/// var sz = serializer(writer, .{}); +/// var vec2 = try sz.startStruct(.{}); +/// try vec2.field("x", 1.5, .{}); +/// try vec2.fieldPrefix(); +/// try sz.value(2.5); +/// try vec2.finish(); +/// ``` +pub fn Serializer(Writer: type) type { + return struct { + const Self = @This(); + + options: SerializerOptions, + indent_level: u8, + writer: Writer, + + /// Initialize a serializer. + fn init(writer: Writer, options: SerializerOptions) Self { + return .{ + .options = options, + .writer = writer, + .indent_level = 0, + }; + } + + /// Serialize a value, similar to `serialize`. + pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptime assert(!typeIsRecursive(@TypeOf(val))); + return self.valueArbitraryDepth(val, options); + } + + /// Serialize a value, similar to `serializeMaxDepth`. + pub fn valueMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + return self.valueArbitraryDepth(val, options); + } + + /// Serialize a value, similar to `serializeArbitraryDepth`. + pub fn valueArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + comptime assert(canSerializeType(@TypeOf(val))); + switch (@typeInfo(@TypeOf(val))) { + .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { + self.codePoint(c) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .float, .comptime_float => try self.float(val), + .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), + .enum_literal => try self.ident(@tagName(val)), + .@"enum" => try self.ident(@tagName(val)), + .void => try self.writer.writeAll("{}"), + .pointer => |pointer| { + // Try to serialize as a string + const item: ?type = switch (@typeInfo(pointer.child)) { + .array => |array| array.child, + else => if (pointer.size == .slice) pointer.child else null, + }; + if (item == u8 and + (pointer.sentinel() == null or pointer.sentinel() == 0) and + !options.emit_strings_as_containers) + { + return try self.string(val); + } + + // Serialize as either a tuple or as the child type + switch (pointer.size) { + .slice => try self.tupleImpl(val, options), + .one => try self.valueArbitraryDepth(val.*, options), + else => comptime unreachable, + } + }, + .array => { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = val.len } }, + ); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, + ); + inline for (val) |field_value| { + try container.fieldArbitraryDepth(field_value, options); + } + try container.finish(); + } else { + // Decide which fields to emit + const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { + break :b .{ @"struct".fields.len, @splat(false) }; + } else b: { + var fields = @"struct".fields.len; + var skipped: [@"struct".fields.len]bool = @splat(false); + inline for (@"struct".fields, &skipped) |field_info, *skip| { + if (field_info.default_value_ptr) |ptr| { + const default: *const field_info.type = @ptrCast(@alignCast(ptr)); + const field_value = @field(val, field_info.name); + if (std.meta.eql(field_value, default.*)) { + skip.* = true; + fields -= 1; + } + } + } + break :b .{ fields, skipped }; + }; + + // Emit those fields + var container = try self.startStruct( + .{ .whitespace_style = .{ .fields = fields } }, + ); + inline for (@"struct".fields, skipped) |field_info, skip| { + if (!skip) { + try container.fieldArbitraryDepth( + field_info.name, + @field(val, field_info.name), + options, + ); + } + } + try container.finish(); + }, + .@"union" => |@"union"| { + comptime assert(@"union".tag_type != null); + var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + switch (val) { + inline else => |pl, tag| try container.fieldArbitraryDepth( + @tagName(tag), + pl, + options, + ), + } + try container.finish(); + }, + .optional => if (val) |inner| { + try self.valueArbitraryDepth(inner, options); + } else { + try self.writer.writeAll("null"); + }, + .vector => |vector| { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = vector.len } }, + ); + for (0..vector.len) |i| { + try container.fieldArbitraryDepth(val[i], options); + } + try container.finish(); + }, + + else => comptime unreachable, + } + } + + /// Serialize an integer. + pub fn int(self: *Self, val: anytype) Writer.Error!void { + try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); + } + + /// Serialize a float. + pub fn float(self: *Self, val: anytype) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .float => if (std.math.isNan(val)) { + return self.writer.writeAll("nan"); + } else if (std.math.isPositiveInf(val)) { + return self.writer.writeAll("inf"); + } else if (std.math.isNegativeInf(val)) { + return self.writer.writeAll("-inf"); + } else { + try std.fmt.format(self.writer, "{d}", .{val}); + }, + .comptime_float => try std.fmt.format(self.writer, "{d}", .{val}), + else => comptime unreachable, + } + } + + /// Serialize `name` as an identifier prefixed with `.`. + /// + /// Escapes the identifier if necessary. + pub fn ident(self: *Self, name: []const u8) Writer.Error!void { + try self.writer.print(".{p_}", .{std.zig.fmtId(name)}); + } + + /// Serialize `val` as a Unicode codepoint. + /// + /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. + pub fn codePoint( + self: *Self, + val: u21, + ) (Writer.Error || error{InvalidCodepoint})!void { + var buf: [8]u8 = undefined; + const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; + const str = buf[0..len]; + try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); + } + + /// Like `value`, but always serializes `val` as a tuple. + /// + /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. + pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptime assert(!typeIsRecursive(@TypeOf(val))); + try self.tupleArbitraryDepth(val, options); + } + + /// Like `tuple`, but recursive types are allowed. + /// + /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. + pub fn tupleMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + try self.tupleArbitraryDepth(val, options); + } + + /// Like `tuple`, but recursive types are allowed. + /// + /// It is the caller's responsibility to ensure that `val` does not contain cycles. + pub fn tupleArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.tupleImpl(val, options); + } + + fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptime assert(canSerializeType(@TypeOf(val))); + switch (@typeInfo(@TypeOf(val))) { + .@"struct" => { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + inline for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .pointer, .array => { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + else => comptime unreachable, + } + } + + /// Like `value`, but always serializes `val` as a string. + pub fn string(self: *Self, val: []const u8) Writer.Error!void { + try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)}); + } + + /// Options for formatting multiline strings. + pub const MultilineStringOptions = struct { + /// If top level is true, whitespace before and after the multiline string is elided. + /// If it is true, a newline is printed, then the value, followed by a newline, and if + /// whitespace is true any necessary indentation follows. + top_level: bool = false, + }; + + /// Like `value`, but always serializes to a multiline string literal. + /// + /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, + /// since multiline strings cannot represent CR without a following newline. + pub fn multilineString( + self: *Self, + val: []const u8, + options: MultilineStringOptions, + ) (Writer.Error || error{InnerCarriageReturn})!void { + // Make sure the string does not contain any carriage returns not followed by a newline + var i: usize = 0; + while (i < val.len) : (i += 1) { + if (val[i] == '\r') { + if (i + 1 < val.len) { + if (val[i + 1] == '\n') { + i += 1; + continue; + } + } + return error.InnerCarriageReturn; + } + } + + if (!options.top_level) { + try self.newline(); + try self.indent(); + } + + try self.writer.writeAll("\\\\"); + for (val) |c| { + if (c != '\r') { + try self.writer.writeByte(c); // We write newlines here even if whitespace off + if (c == '\n') { + try self.indent(); + try self.writer.writeAll("\\\\"); + } + } + } + + if (!options.top_level) { + try self.writer.writeByte('\n'); // Even if whitespace off + try self.indent(); + } + } + + /// Create a `Struct` for writing ZON structs field by field. + pub fn startStruct( + self: *Self, + options: SerializeContainerOptions, + ) Writer.Error!Struct { + return Struct.start(self, options); + } + + /// Creates a `Tuple` for writing ZON tuples field by field. + pub fn startTuple( + self: *Self, + options: SerializeContainerOptions, + ) Writer.Error!Tuple { + return Tuple.start(self, options); + } + + fn indent(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByteNTimes(' ', 4 * self.indent_level); + } + } + + fn newline(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte('\n'); + } + } + + fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void { + if (self.containerShouldWrap(len)) { + try self.newline(); + } else { + try self.space(); + } + } + + fn space(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte(' '); + } + } + + /// Writes ZON tuples field by field. + pub const Tuple = struct { + container: Container, + + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple { + return .{ + .container = try Container.start(parent, .anon, options), + }; + } + + /// Finishes serializing the tuple. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Tuple) Writer.Error!void { + try self.container.finish(); + self.* = undefined; + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. + pub fn field( + self: *Tuple, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.field(null, val, options); + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. + pub fn fieldMaxDepth( + self: *Tuple, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try self.container.fieldMaxDepth(null, val, options, depth); + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by + /// `valueArbitraryDepth`. + pub fn fieldArbitraryDepth( + self: *Tuple, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.fieldArbitraryDepth(null, val, options); + } + + /// Print a field prefix. This prints any necessary commas, and whitespace as + /// configured. Useful if you want to serialize the field value yourself. + pub fn fieldPrefix(self: *Tuple) Writer.Error!void { + try self.container.fieldPrefix(null); + } + }; + + /// Writes ZON structs field by field. + pub const Struct = struct { + container: Container, + + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct { + return .{ + .container = try Container.start(parent, .named, options), + }; + } + + /// Finishes serializing the struct. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Struct) Writer.Error!void { + try self.container.finish(); + self.* = undefined; + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. + pub fn field( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.field(name, val, options); + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. + pub fn fieldMaxDepth( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try self.container.fieldMaxDepth(name, val, options, depth); + } + + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by + /// `valueArbitraryDepth`. + pub fn fieldArbitraryDepth( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.fieldArbitraryDepth(name, val, options); + } + + /// Print a field prefix. This prints any necessary commas, the field name (escaped if + /// necessary) and whitespace as configured. Useful if you want to serialize the field + /// value yourself. + pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void { + try self.container.fieldPrefix(name); + } + }; + + const Container = struct { + const FieldStyle = enum { named, anon }; + + serializer: *Self, + field_style: FieldStyle, + options: SerializeContainerOptions, + empty: bool, + + fn start( + sz: *Self, + field_style: FieldStyle, + options: SerializeContainerOptions, + ) Writer.Error!Container { + if (options.shouldWrap()) sz.indent_level +|= 1; + try sz.writer.writeAll(".{"); + return .{ + .serializer = sz, + .field_style = field_style, + .options = options, + .empty = true, + }; + } + + fn finish(self: *Container) Writer.Error!void { + if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; + if (!self.empty) { + if (self.options.shouldWrap()) { + if (self.serializer.options.whitespace) { + try self.serializer.writer.writeByte(','); + } + try self.serializer.newline(); + try self.serializer.indent(); + } else if (!self.shouldElideSpaces()) { + try self.serializer.space(); + } + } + try self.serializer.writer.writeByte('}'); + self.* = undefined; + } + + fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void { + if (!self.empty) { + try self.serializer.writer.writeByte(','); + } + self.empty = false; + if (self.options.shouldWrap()) { + try self.serializer.newline(); + } else if (!self.shouldElideSpaces()) { + try self.serializer.space(); + } + if (self.options.shouldWrap()) try self.serializer.indent(); + if (name) |n| { + try self.serializer.ident(n); + try self.serializer.space(); + try self.serializer.writer.writeByte('='); + try self.serializer.space(); + } + } + + fn field( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + comptime assert(!typeIsRecursive(@TypeOf(val))); + try self.fieldArbitraryDepth(name, val, options); + } + + fn fieldMaxDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + try self.fieldArbitraryDepth(name, val, options); + } + + fn fieldArbitraryDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.fieldPrefix(name); + try self.serializer.valueArbitraryDepth(val, options); + } + + fn shouldElideSpaces(self: *const Container) bool { + return switch (self.options.whitespace_style) { + .fields => |fields| self.field_style != .named and fields == 1, + else => false, + }; + } + }; + }; +} + +/// Creates a new `Serializer` with the given writer and options. +pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) { + return .init(writer, options); +} + +fn expectSerializeEqual( + expected: []const u8, + value: anytype, + options: SerializeOptions, +) !void { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + try serialize(value, options, buf.writer()); + try std.testing.expectEqualStrings(expected, buf.items); +} + +test "std.zon stringify whitespace, high level API" { + try expectSerializeEqual(".{}", .{}, .{}); + try expectSerializeEqual(".{}", .{}, .{ .whitespace = false }); + + try expectSerializeEqual(".{1}", .{1}, .{}); + try expectSerializeEqual(".{1}", .{1}, .{ .whitespace = false }); + + try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{}); + try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false }); + + try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{}); + try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); + + try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{}); + try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false }); + + try expectSerializeEqual(".{ 1, 2 }", .{ 1, 2 }, .{}); + try expectSerializeEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false }); + + try expectSerializeEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{}); + try expectSerializeEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false }); + + try expectSerializeEqual(".{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{}); + try expectSerializeEqual(".{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false }); + + try expectSerializeEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{}); + try expectSerializeEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false }); + + try expectSerializeEqual( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , .{ 1, 2, 3 }, .{}); + try expectSerializeEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false }); + + try expectSerializeEqual( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , @as([3]u32, .{ 1, 2, 3 }), .{}); + try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false }); + + try expectSerializeEqual( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , @as([]const u32, &.{ 1, 2, 3 }), .{}); + try expectSerializeEqual( + ".{1,2,3}", + @as([]const u32, &.{ 1, 2, 3 }), + .{ .whitespace = false }, + ); + + try expectSerializeEqual( + \\.{ + \\ .x = 1, + \\ .y = 2, + \\ .z = 3, + \\} + , .{ .x = 1, .y = 2, .z = 3 }, .{}); + try expectSerializeEqual( + ".{.x=1,.y=2,.z=3}", + .{ .x = 1, .y = 2, .z = 3 }, + .{ .whitespace = false }, + ); + + const Union = union(enum) { a: bool, b: i32, c: u8 }; + + try expectSerializeEqual(".{ .b = 1 }", Union{ .b = 1 }, .{}); + try expectSerializeEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false }); + + // Nested indentation where outer object doesn't wrap + try expectSerializeEqual( + \\.{ .inner = .{ + \\ 1, + \\ 2, + \\ 3, + \\} } + , .{ .inner = .{ 1, 2, 3 } }, .{}); +} + +test "std.zon stringify whitespace, low level API" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + inline for (.{ true, false }) |whitespace| { + sz.options = .{ .whitespace = whitespace }; + + // Empty containers + { + var container = try sz.startStruct(.{}); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{}); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 0 } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 0 } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); + } + + // Size 1 + { + var container = try sz.startStruct(.{}); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{}); + try container.field(1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{1}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + // We get extra spaces here, since we didn't know up front that there would only be one + // field. + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.field(1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{1}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 1 } }); + try container.field(1, .{}); + try container.finish(); + try std.testing.expectEqualStrings(".{1}", buf.items); + buf.clearRetainingCapacity(); + } + + // Size 2 + { + var container = try sz.startStruct(.{}); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\ .b = 2, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{}); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\ 2, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 2 } }); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 2 } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buf.items); + } + buf.clearRetainingCapacity(); + } + + // Size 3 + { + var container = try sz.startStruct(.{}); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.field("c", 3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\ .b = 2, + \\ .c = 3, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{}); + try container.field(1, .{}); + try container.field(2, .{}); + try container.field(3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.field("c", 3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.field(3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 3 } }); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.field("c", 3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\ .b = 2, + \\ .c = 3, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 3 } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.field(3, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , buf.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); + } + buf.clearRetainingCapacity(); + } + + // Nested objects where the outer container doesn't wrap but the inner containers do + { + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.field("first", .{ 1, 2, 3 }, .{}); + try container.field("second", .{ 4, 5, 6 }, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ .first = .{ + \\ 1, + \\ 2, + \\ 3, + \\}, .second = .{ + \\ 4, + \\ 5, + \\ 6, + \\} } + , buf.items); + } else { + try std.testing.expectEqualStrings( + ".{.first=.{1,2,3},.second=.{4,5,6}}", + buf.items, + ); + } + buf.clearRetainingCapacity(); + } + } +} + +test "std.zon stringify utf8 codepoints" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Printable ASCII + try sz.int('a'); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.codePoint('a'); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('a', .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('a', .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + // Short escaped codepoint + try sz.int('\n'); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); + + try sz.codePoint('\n'); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); + + // Large codepoint + try sz.int('âš¡'); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + try sz.codePoint('âš¡'); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + // Invalid codepoint + try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1)); + + try sz.int(0x110000 + 1); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + // Valid codepoint, not a codepoint type + try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + // Make sure value options are passed to children + try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items); + buf.clearRetainingCapacity(); +} + +test "std.zon stringify strings" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Minimal case + try sz.string("abcâš¡\n"); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); + buf.clearRetainingCapacity(); + + try sz.tuple("abcâš¡\n", .{}); + try std.testing.expectEqualStrings( + \\.{ + \\ 97, + \\ 98, + \\ 99, + \\ 226, + \\ 154, + \\ 161, + \\ 10, + \\} + , buf.items); + buf.clearRetainingCapacity(); + + try sz.value("abcâš¡\n", .{}); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); + buf.clearRetainingCapacity(); + + try sz.value("abcâš¡\n", .{ .emit_strings_as_containers = true }); + try std.testing.expectEqualStrings( + \\.{ + \\ 97, + \\ 98, + \\ 99, + \\ 226, + \\ 154, + \\ 161, + \\ 10, + \\} + , buf.items); + buf.clearRetainingCapacity(); + + // Value options are inherited by children + try sz.value(.{ .str = "abc" }, .{}); + try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); + try std.testing.expectEqualStrings( + \\.{ .str = .{ + \\ 97, + \\ 98, + \\ 99, + \\} } + , buf.items); + buf.clearRetainingCapacity(); + + // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can + // round trip correctly. + try sz.value("abc".*, .{}); + try std.testing.expectEqualStrings( + \\.{ + \\ 97, + \\ 98, + \\ 99, + \\} + , buf.items); + buf.clearRetainingCapacity(); +} + +test "std.zon stringify multiline strings" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + inline for (.{ true, false }) |whitespace| { + sz.options.whitespace = whitespace; + + { + try sz.multilineString("", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("abcâš¡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abcâš¡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("abcâš¡\ndef", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("abcâš¡\r\ndef", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("\nabcâš¡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("\r\nabcâš¡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try sz.multilineString("abc\ndef", .{}); + if (whitespace) { + try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items); + } else { + try std.testing.expectEqualStrings("\\\\abc\n\\\\def\n", buf.items); + } + buf.clearRetainingCapacity(); + } + + { + const str: []const u8 = &.{ 'a', '\r', 'c' }; + try sz.string(str); + try std.testing.expectEqualStrings("\"a\\rc\"", buf.items); + buf.clearRetainingCapacity(); + } + + { + try std.testing.expectError( + error.InnerCarriageReturn, + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), + ); + try std.testing.expectError( + error.InnerCarriageReturn, + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), + ); + try std.testing.expectError( + error.InnerCarriageReturn, + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + } + } +} + +test "std.zon stringify skip default fields" { + const Struct = struct { + x: i32 = 2, + y: i8, + z: u32 = 4, + inner1: struct { a: u8 = 'z', b: u8 = 'y', c: u8 } = .{ + .a = '1', + .b = '2', + .c = '3', + }, + inner2: struct { u8, u8, u8 } = .{ + 'a', + 'b', + 'c', + }, + inner3: struct { u8, u8, u8 } = .{ + 'a', + 'b', + 'c', + }, + }; + + // Not skipping if not set + try expectSerializeEqual( + \\.{ + \\ .x = 2, + \\ .y = 3, + \\ .z = 4, + \\ .inner1 = .{ + \\ .a = '1', + \\ .b = '2', + \\ .c = '3', + \\ }, + \\ .inner2 = .{ + \\ 'a', + \\ 'b', + \\ 'c', + \\ }, + \\ .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\ }, + \\} + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = '1', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ .emit_codepoint_literals = .always }, + ); + + // Top level defaults + try expectSerializeEqual( + \\.{ .y = 3, .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\} } + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = '1', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ + .emit_default_optional_fields = false, + .emit_codepoint_literals = .always, + }, + ); + + // Inner types having defaults, and defaults changing the number of fields affecting the + // formatting + try expectSerializeEqual( + \\.{ + \\ .y = 3, + \\ .inner1 = .{ .b = '2', .c = '3' }, + \\ .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\ }, + \\} + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = 'z', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ + .emit_default_optional_fields = false, + .emit_codepoint_literals = .always, + }, + ); + + const DefaultStrings = struct { + foo: []const u8 = "abc", + }; + try expectSerializeEqual( + \\.{} + , + DefaultStrings{ .foo = "abc" }, + .{ .emit_default_optional_fields = false }, + ); + try expectSerializeEqual( + \\.{ .foo = "abcd" } + , + DefaultStrings{ .foo = "abcd" }, + .{ .emit_default_optional_fields = false }, + ); +} + +test "std.zon depth limits" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + + const Recurse = struct { r: []const @This() }; + + // Normal operation + try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); + try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); + buf.clearRetainingCapacity(); + + try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer()); + try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); + buf.clearRetainingCapacity(); + + // Max depth failing on non recursive type + try std.testing.expectError( + error.ExceededMaxDepth, + serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + // Max depth passing on recursive type + { + const maybe_recurse = Recurse{ .r = &.{} }; + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); + try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); + buf.clearRetainingCapacity(); + } + + // Unchecked passing on recursive type + { + const maybe_recurse = Recurse{ .r = &.{} }; + try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); + try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); + buf.clearRetainingCapacity(); + } + + // Max depth failing on recursive type due to depth + { + var maybe_recurse = Recurse{ .r = &.{} }; + maybe_recurse.r = &.{.{ .r = &.{} }}; + try std.testing.expectError( + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + } + + // Same but for a slice + { + var temp: [1]Recurse = .{.{ .r = &.{} }}; + const maybe_recurse: []const Recurse = &temp; + + try std.testing.expectError( + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + var sz = serializer(buf.writer(), .{}); + + try std.testing.expectError( + error.ExceededMaxDepth, + sz.tupleMaxDepth(maybe_recurse, .{}, 2), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + try sz.tupleArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); + buf.clearRetainingCapacity(); + } + + // A slice succeeding + { + var temp: [1]Recurse = .{.{ .r = &.{} }}; + const maybe_recurse: []const Recurse = &temp; + + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); + buf.clearRetainingCapacity(); + + var sz = serializer(buf.writer(), .{}); + + try sz.tupleMaxDepth(maybe_recurse, .{}, 3); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); + buf.clearRetainingCapacity(); + + try sz.tupleArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); + buf.clearRetainingCapacity(); + } + + // Max depth failing on recursive type due to recursion + { + var temp: [1]Recurse = .{.{ .r = &.{} }}; + temp[0].r = &temp; + const maybe_recurse: []const Recurse = &temp; + + try std.testing.expectError( + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + var sz = serializer(buf.writer(), .{}); + try std.testing.expectError( + error.ExceededMaxDepth, + sz.tupleMaxDepth(maybe_recurse, .{}, 128), + ); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + } + + // Max depth on other parts of the lower level API + { + var sz = serializer(buf.writer(), .{}); + + const maybe_recurse: []const Recurse = &.{}; + + try std.testing.expectError(error.ExceededMaxDepth, sz.valueMaxDepth(1, .{}, 0)); + try sz.valueMaxDepth(2, .{}, 1); + try sz.value(3, .{}); + try sz.valueArbitraryDepth(maybe_recurse, .{}); + + var s = try sz.startStruct(.{}); + try std.testing.expectError(error.ExceededMaxDepth, s.fieldMaxDepth("a", 1, .{}, 0)); + try s.fieldMaxDepth("b", 4, .{}, 1); + try s.field("c", 5, .{}); + try s.fieldArbitraryDepth("d", maybe_recurse, .{}); + try s.finish(); + + var t = try sz.startTuple(.{}); + try std.testing.expectError(error.ExceededMaxDepth, t.fieldMaxDepth(1, .{}, 0)); + try t.fieldMaxDepth(6, .{}, 1); + try t.field(7, .{}); + try t.fieldArbitraryDepth(maybe_recurse, .{}); + try t.finish(); + + var a = try sz.startTuple(.{}); + try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0)); + try a.fieldMaxDepth(8, .{}, 1); + try a.field(9, .{}); + try a.fieldArbitraryDepth(maybe_recurse, .{}); + try a.finish(); + + try std.testing.expectEqualStrings( + \\23.{}.{ + \\ .b = 4, + \\ .c = 5, + \\ .d = .{}, + \\}.{ + \\ 6, + \\ 7, + \\ .{}, + \\}.{ + \\ 8, + \\ 9, + \\ .{}, + \\} + , buf.items); + } +} + +test "std.zon stringify primitives" { + // Issue: https://github.com/ziglang/zig/issues/20880 + if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; + + try expectSerializeEqual( + \\.{ + \\ .a = 1.5, + \\ .b = 0.3333333333333333333333333333333333, + \\ .c = 3.1415926535897932384626433832795028, + \\ .d = 0, + \\ .e = -0, + \\ .f = inf, + \\ .g = -inf, + \\ .h = nan, + \\} + , + .{ + .a = @as(f128, 1.5), // Make sure explicit f128s work + .b = 1.0 / 3.0, + .c = std.math.pi, + .d = 0.0, + .e = -0.0, + .f = std.math.inf(f32), + .g = -std.math.inf(f32), + .h = std.math.nan(f32), + }, + .{}, + ); + + try expectSerializeEqual( + \\.{ + \\ .a = 18446744073709551616, + \\ .b = -18446744073709551616, + \\ .c = 680564733841876926926749214863536422912, + \\ .d = -680564733841876926926749214863536422912, + \\ .e = 0, + \\} + , + .{ + .a = 18446744073709551616, + .b = -18446744073709551616, + .c = 680564733841876926926749214863536422912, + .d = -680564733841876926926749214863536422912, + .e = 0, + }, + .{}, + ); + + try expectSerializeEqual( + \\.{ + \\ .a = true, + \\ .b = false, + \\ .c = .foo, + \\ .e = null, + \\} + , + .{ + .a = true, + .b = false, + .c = .foo, + .e = null, + }, + .{}, + ); + + const Struct = struct { x: f32, y: f32 }; + try expectSerializeEqual( + ".{ .a = .{ .x = 1, .y = 2 }, .b = null }", + .{ + .a = @as(?Struct, .{ .x = 1, .y = 2 }), + .b = @as(?Struct, null), + }, + .{}, + ); + + const E = enum(u8) { + foo, + bar, + }; + try expectSerializeEqual( + ".{ .a = .foo, .b = .foo }", + .{ + .a = .foo, + .b = E.foo, + }, + .{}, + ); +} + +test "std.zon stringify ident" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{}); + try sz.ident("a"); + try std.testing.expectEqualStrings(".a", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("foo_1"); + try std.testing.expectEqualStrings(".foo_1", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("_foo_1"); + try std.testing.expectEqualStrings("._foo_1", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("foo bar"); + try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("1foo"); + try std.testing.expectEqualStrings(".@\"1foo\"", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("var"); + try std.testing.expectEqualStrings(".@\"var\"", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("true"); + try std.testing.expectEqualStrings(".true", buf.items); + buf.clearRetainingCapacity(); + + try sz.ident("_"); + try std.testing.expectEqualStrings("._", buf.items); + buf.clearRetainingCapacity(); + + const Enum = enum { + @"foo bar", + }; + try expectSerializeEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{ + .@"var" = .@"foo bar", + .@"1" = Enum.@"foo bar", + }, .{}); +} + +test "std.zon stringify as tuple" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Tuples + try sz.tuple(.{ 1, 2 }, .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); + + // Slice + try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); + + // Array + try sz.tuple([2]u8{ 1, 2 }, .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); +} + +test "std.zon stringify as float" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Comptime float + try sz.float(2.5); + try std.testing.expectEqualStrings("2.5", buf.items); + buf.clearRetainingCapacity(); + + // Sized float + try sz.float(@as(f32, 2.5)); + try std.testing.expectEqualStrings("2.5", buf.items); + buf.clearRetainingCapacity(); +} + +test "std.zon stringify vector" { + try expectSerializeEqual( + \\.{ + \\ .{}, + \\ .{ + \\ true, + \\ false, + \\ true, + \\ }, + \\ .{}, + \\ .{ + \\ 1.5, + \\ 2.5, + \\ 3.5, + \\ }, + \\ .{}, + \\ .{ + \\ 2, + \\ 4, + \\ 6, + \\ }, + \\ .{ 1, 2 }, + \\ .{ + \\ 3, + \\ 4, + \\ null, + \\ }, + \\} + , + .{ + @Vector(0, bool){}, + @Vector(3, bool){ true, false, true }, + @Vector(0, f32){}, + @Vector(3, f32){ 1.5, 2.5, 3.5 }, + @Vector(0, u8){}, + @Vector(3, u8){ 2, 4, 6 }, + @Vector(2, *const u8){ &1, &2 }, + @Vector(3, ?*const u8){ &3, &4, null }, + }, + .{}, + ); +} + +test "std.zon pointers" { + // Primitive with varying levels of pointers + try expectSerializeEqual("10", &@as(u32, 10), .{}); + try expectSerializeEqual("10", &&@as(u32, 10), .{}); + try expectSerializeEqual("10", &&&@as(u32, 10), .{}); + + // Primitive optional with varying levels of pointers + try expectSerializeEqual("10", @as(?*const u32, &10), .{}); + try expectSerializeEqual("null", @as(?*const u32, null), .{}); + try expectSerializeEqual("10", @as(?*const u32, &10), .{}); + try expectSerializeEqual("null", @as(*const ?u32, &null), .{}); + + try expectSerializeEqual("10", @as(?*const *const u32, &&10), .{}); + try expectSerializeEqual("null", @as(?*const *const u32, null), .{}); + try expectSerializeEqual("10", @as(*const ?*const u32, &&10), .{}); + try expectSerializeEqual("null", @as(*const ?*const u32, &null), .{}); + try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{}); + try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{}); + + try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{}); + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const val: ?*const Outer = &.{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + try expectSerializeEqual( + \\.{ .f1 = .{ .f1 = null, .f2 = "foo" }, .f2 = null } + , val, .{}); + } +} diff --git a/src/Compilation.zig b/src/Compilation.zig index 1362b16cc992..dc1d7df320f4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2220,7 +2220,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count()); for (zcu.import_table.values()) |file_index| { if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue; - comp.astgen_work_queue.writeItemAssumeCapacity(file_index); + const file = zcu.fileByIndex(file_index); + if (file.getMode() == .zig) { + comp.astgen_work_queue.writeItemAssumeCapacity(file_index); + } } if (comp.file_system_inputs) |fsi| { for (zcu.import_table.values()) |file_index| { @@ -3206,10 +3209,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { if (error_msg) |msg| { try addModuleErrorMsg(zcu, &bundle, msg.*); } else { - // Must be ZIR errors. Note that this may include AST errors. - // addZirErrorMessages asserts that the tree is loaded. - _ = try file.getTree(gpa); - try addZirErrorMessages(&bundle, file); + // Must be ZIR or Zoir errors. Note that this may include AST errors. + _ = try file.getTree(gpa); // Tree must be loaded. + if (file.zir_loaded) { + try addZirErrorMessages(&bundle, file); + } else if (file.zoir != null) { + try addZoirErrorMessages(&bundle, file); + } else { + // Either Zir or Zoir must have been loaded. + unreachable; + } } } var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: { @@ -3623,6 +3632,15 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { return eb.addZirErrorMessages(file.zir, file.tree, file.source, src_path); } +pub fn addZoirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { + assert(file.source_loaded); + assert(file.tree_loaded); + const gpa = eb.gpa; + const src_path = try file.fullPath(gpa); + defer gpa.free(src_path); + return eb.addZoirErrorMessages(file.zoir.?, file.tree, file.source, src_path); +} + pub fn performAllTheWork( comp: *Compilation, main_progress_node: std.Progress.Node, @@ -4272,6 +4290,7 @@ fn workerAstGenFile( wg: *WaitGroup, src: Zcu.AstGenSrc, ) void { + assert(file.getMode() == .zig); const child_prog_node = prog_node.start(file.sub_file_path, 0); defer child_prog_node.end(); @@ -4325,7 +4344,7 @@ fn workerAstGenFile( const imported_path_digest = pt.zcu.filePathDigest(res.file_index); break :blk .{ res, imported_path_digest }; }; - if (import_result.is_new) { + if (import_result.is_new and import_result.file.getMode() == .zig) { log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{ file.sub_file_path, import_path, import_result.file.sub_file_path, }); diff --git a/src/InternPool.zig b/src/InternPool.zig index 3668228e07bc..4a6b5a86d2a7 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -4389,7 +4389,7 @@ pub const LoadedEnumType = struct { // Auto-numbered enum. Convert `int_tag_val` to field index. const field_index = switch (ip.indexToKey(int_tag_val).int.storage) { inline .u64, .i64 => |x| std.math.cast(u32, x) orelse return null, - .big_int => |x| x.to(u32) catch return null, + .big_int => |x| x.toInt(u32) catch return null, .lazy_align, .lazy_size => unreachable, }; return if (field_index < self.names.len) field_index else null; @@ -7957,7 +7957,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u8, - .data = big_int.to(u8) catch unreachable, + .data = big_int.toInt(u8) catch unreachable, }); break :b; }, @@ -7974,7 +7974,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u16, - .data = big_int.to(u16) catch unreachable, + .data = big_int.toInt(u16) catch unreachable, }); break :b; }, @@ -7991,7 +7991,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u32, - .data = big_int.to(u32) catch unreachable, + .data = big_int.toInt(u32) catch unreachable, }); break :b; }, @@ -8006,7 +8006,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .i32_type => switch (int.storage) { .big_int => |big_int| { - const casted = big_int.to(i32) catch unreachable; + const casted = big_int.toInt(i32) catch unreachable; items.appendAssumeCapacity(.{ .tag = .int_i32, .data = @as(u32, @bitCast(casted)), @@ -8024,7 +8024,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .usize_type => switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_usize, .data = casted, @@ -8045,14 +8045,14 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .comptime_int_type => switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_comptime_int_u32, .data = casted, }); break :b; } else |_| {} - if (big_int.to(i32)) |casted| { + if (big_int.toInt(i32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_comptime_int_i32, .data = @as(u32, @bitCast(casted)), @@ -8082,7 +8082,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All } switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_small, .data = try addExtra(extra, IntSmall{ diff --git a/src/Sema.zig b/src/Sema.zig index 30db9e90009b..5f4463a9d586 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 LowerZon = @import("Sema/LowerZon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -5790,7 +5791,7 @@ fn addNullTerminatedStrLit(sema: *Sema, string: InternPool.NullTerminatedString) return sema.addStrLit(string.toString(), string.length(&sema.pt.zcu.intern_pool)); } -fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref { +pub fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref { const pt = sema.pt; const array_ty = try pt.arrayType(.{ .len = len, @@ -13964,9 +13965,10 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const pt = sema.pt; const zcu = pt.zcu; - const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok; + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok; + const extra = sema.code.extraData(Zir.Inst.Import, inst_data.payload_index).data; const operand_src = block.tokenOffset(inst_data.src_tok); - const operand = inst_data.get(sema.code); + const operand = sema.code.nullTerminatedString(extra.path); const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) { error.ImportOutsideModulePath => { @@ -13983,12 +13985,42 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) }); }, }; - try sema.declareDependency(.{ .file = result.file_index }); - try pt.ensureFileAnalyzed(result.file_index); - const ty = zcu.fileRootType(result.file_index); - try sema.declareDependency(.{ .interned = ty }); - try sema.addTypeReferenceEntry(operand_src, ty); - return Air.internedToRef(ty); + switch (result.file.getMode()) { + .zig => { + try sema.declareDependency(.{ .file = result.file_index }); + try pt.ensureFileAnalyzed(result.file_index); + const ty = zcu.fileRootType(result.file_index); + try sema.declareDependency(.{ .interned = ty }); + try sema.addTypeReferenceEntry(operand_src, ty); + return Air.internedToRef(ty); + }, + .zon => { + _ = result.file.getTree(zcu.gpa) catch |err| { + // TODO: these errors are file system errors; make sure an update() will + // retry this and not cache the file system error, which may be transient. + return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ result.file.sub_file_path, @errorName(err) }); + }; + + if (extra.res_ty == .none) { + return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); + } + const res_ty_inst = try sema.resolveInst(extra.res_ty); + const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst); + if (res_ty.isGenericPoison()) { + return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); + } + + const interned = try LowerZon.run( + sema, + result.file, + result.file_index, + res_ty, + operand_src, + block, + ); + return Air.internedToRef(interned); + }, + } } fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig new file mode 100644 index 000000000000..2b2d16b90a09 --- /dev/null +++ b/src/Sema/LowerZon.zig @@ -0,0 +1,858 @@ +const std = @import("std"); +const Zcu = @import("../Zcu.zig"); +const Sema = @import("../Sema.zig"); +const Air = @import("../Air.zig"); +const InternPool = @import("../InternPool.zig"); +const Type = @import("../Type.zig"); +const Value = @import("../Value.zig"); +const Zir = std.zig.Zir; +const AstGen = std.zig.AstGen; +const CompileError = Zcu.CompileError; +const Ast = std.zig.Ast; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const File = Zcu.File; +const LazySrcLoc = Zcu.LazySrcLoc; +const Ref = std.zig.Zir.Inst.Ref; +const NullTerminatedString = InternPool.NullTerminatedString; +const NumberLiteralError = std.zig.number_literal.Error; +const NodeIndex = std.zig.Ast.Node.Index; +const Zoir = std.zig.Zoir; + +const LowerZon = @This(); + +sema: *Sema, +file: *File, +file_index: Zcu.File.Index, +import_loc: LazySrcLoc, +block: *Sema.Block, +base_node_inst: InternPool.TrackedInst.Index, + +/// Lowers the given file as ZON. +pub fn run( + sema: *Sema, + file: *File, + file_index: Zcu.File.Index, + res_ty: Type, + import_loc: LazySrcLoc, + block: *Sema.Block, +) CompileError!InternPool.Index { + const pt = sema.pt; + + _ = try file.getZoir(pt.zcu); + + const tracked_inst = try pt.zcu.intern_pool.trackZir(pt.zcu.gpa, pt.tid, .{ + .file = file_index, + .inst = .main_struct_inst, // this is the only trackable instruction in a ZON file + }); + + var lower_zon: LowerZon = .{ + .sema = sema, + .file = file, + .file_index = file_index, + .import_loc = import_loc, + .block = block, + .base_node_inst = tracked_inst, + }; + + try lower_zon.checkType(res_ty); + + return lower_zon.lowerExpr(.root, res_ty); +} + +/// Validate that `ty` is a valid ZON type. If not, emit a compile error. +/// i.e. no nested optionals, no error sets, etc. +fn checkType(self: *LowerZon, ty: Type) !void { + var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty; + try self.checkTypeInner(ty, null, &visited); +} + +fn checkTypeInner( + self: *LowerZon, + ty: Type, + parent_opt_ty: ?Type, + /// Visited structs and unions (not tuples). These are tracked because they are the only way in + /// which a type can be self-referential, so must be tracked to avoid loops. Tracking more types + /// consumes memory unnecessarily, and would be complicated by optionals. + /// Allocated into `self.sema.arena`. + visited: *std.AutoHashMapUnmanaged(InternPool.Index, void), +) !void { + const sema = self.sema; + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + switch (ty.zigTypeTag(zcu)) { + .bool, + .int, + .float, + .null, + .@"enum", + .comptime_float, + .comptime_int, + .enum_literal, + => {}, + + .noreturn, + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + => return self.failUnsupportedResultType(ty, null), + + .pointer => { + const ptr_info = ty.ptrInfo(zcu); + if (!ptr_info.flags.is_const) { + return self.failUnsupportedResultType( + ty, + "ZON does not allow mutable pointers", + ); + } + switch (ptr_info.flags.size) { + .one => try self.checkTypeInner( + .fromInterned(ptr_info.child), + parent_opt_ty, // preserved + visited, + ), + .slice => try self.checkTypeInner( + .fromInterned(ptr_info.child), + null, + visited, + ), + .many => return self.failUnsupportedResultType(ty, "ZON does not allow many-pointers"), + .c => return self.failUnsupportedResultType(ty, "ZON does not allow C pointers"), + } + }, + .optional => if (parent_opt_ty) |p| { + return self.failUnsupportedResultType(p, "ZON does not allow nested optionals"); + } else try self.checkTypeInner( + ty.optionalChild(zcu), + ty, + visited, + ), + .array, .vector => { + try self.checkTypeInner(ty.childType(zcu), null, visited); + }, + .@"struct" => if (ty.isTuple(zcu)) { + const tuple_info = ip.indexToKey(ty.toIntern()).tuple_type; + const field_types = tuple_info.types.get(ip); + for (field_types) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), null, visited); + } + } else { + const gop = try visited.getOrPut(sema.arena, ty.toIntern()); + if (gop.found_existing) return; + try ty.resolveFields(pt); + const struct_info = zcu.typeToStruct(ty).?; + for (struct_info.field_types.get(ip)) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), null, visited); + } + }, + .@"union" => { + const gop = try visited.getOrPut(sema.arena, ty.toIntern()); + if (gop.found_existing) return; + try ty.resolveFields(pt); + const union_info = zcu.typeToUnion(ty).?; + for (union_info.field_types.get(ip)) |field_type| { + if (field_type != .void_type) { + try self.checkTypeInner(.fromInterned(field_type), null, visited); + } + } + }, + } +} + +fn nodeSrc(self: *LowerZon, node: Zoir.Node.Index) LazySrcLoc { + return .{ + .base_node_inst = self.base_node_inst, + .offset = .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + }; +} + +fn failUnsupportedResultType( + self: *LowerZon, + ty: Type, + opt_note: ?[]const u8, +) error{ AnalysisFail, OutOfMemory } { + @branchHint(.cold); + const sema = self.sema; + const gpa = sema.gpa; + const pt = sema.pt; + return sema.failWithOwnedErrorMsg(self.block, msg: { + const msg = try sema.errMsg(self.import_loc, "type '{}' is not available in ZON", .{ty.fmt(pt)}); + errdefer msg.destroy(gpa); + if (opt_note) |n| try sema.errNote(self.import_loc, msg, "{s}", .{n}); + break :msg msg; + }); +} + +fn fail( + self: *LowerZon, + node: Zoir.Node.Index, + comptime format: []const u8, + args: anytype, +) error{ AnalysisFail, OutOfMemory } { + @branchHint(.cold); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.nodeSrc(node), format, args); + try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); + return self.sema.failWithOwnedErrorMsg(self.block, err_msg); +} + +fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { + const pt = self.sema.pt; + return self.lowerExprInner(node, res_ty) catch |err| switch (err) { + error.WrongType => return self.fail( + node, + "expected type '{}'", + .{res_ty.fmt(pt)}, + ), + else => |e| return e, + }; +} + +fn lowerExprInner( + self: *LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) (CompileError || error{WrongType})!InternPool.Index { + const pt = self.sema.pt; + switch (res_ty.zigTypeTag(pt.zcu)) { + .optional => return pt.intern(.{ + .opt = .{ + .ty = res_ty.toIntern(), + .val = if (node.get(self.file.zoir.?) == .null) b: { + break :b .none; + } else b: { + const child_type = res_ty.optionalChild(pt.zcu); + break :b try self.lowerExprInner(node, child_type); + }, + }, + }), + .pointer => { + const ptr_info = res_ty.ptrInfo(pt.zcu); + switch (ptr_info.flags.size) { + .one => return pt.intern(.{ .ptr = .{ + .ty = res_ty.toIntern(), + .base_addr = .{ + .uav = .{ + .orig_ty = res_ty.toIntern(), + .val = try self.lowerExprInner(node, .fromInterned(ptr_info.child)), + }, + }, + .byte_offset = 0, + } }), + .slice => return self.lowerSlice(node, res_ty), + else => { + // Unsupported pointer type, checked in `lower` + unreachable; + }, + } + }, + .bool => return self.lowerBool(node), + .int, .comptime_int => return self.lowerInt(node, res_ty), + .float, .comptime_float => return self.lowerFloat(node, res_ty), + .null => return self.lowerNull(node), + .@"enum" => return self.lowerEnum(node, res_ty), + .enum_literal => return self.lowerEnumLiteral(node), + .array => return self.lowerArray(node, res_ty), + .@"struct" => return self.lowerStructOrTuple(node, res_ty), + .@"union" => return self.lowerUnion(node, res_ty), + .vector => return self.lowerVector(node, res_ty), + + .type, + .noreturn, + .undefined, + .error_union, + .error_set, + .@"fn", + .@"opaque", + .frame, + .@"anyframe", + .void, + => return self.fail(node, "type '{}' not available in ZON", .{res_ty.fmt(pt)}), + } +} + +fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { + return switch (node.get(self.file.zoir.?)) { + .true => .bool_true, + .false => .bool_false, + else => return error.WrongType, + }; +} + +fn lowerInt( + self: *LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + return switch (node.get(self.file.zoir.?)) { + .int_literal => |int| switch (int) { + .small => |val| { + const rhs: i32 = val; + + // If our result is a fixed size integer, check that our value is not out of bounds + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { + const lhs_info = res_ty.intInfo(self.sema.pt.zcu); + + // If lhs is unsigned and rhs is less than 0, we're out of bounds + if (lhs_info.signedness == .unsigned and rhs < 0) return self.fail( + node, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, + ); + + // If lhs has less than the 32 bits rhs can hold, we need to check the max and + // min values + if (std.math.cast(u5, lhs_info.bits)) |bits| { + const min_int: i32 = if (lhs_info.signedness == .unsigned or bits == 0) b: { + break :b 0; + } else b: { + break :b -(@as(i32, 1) << (bits - 1)); + }; + const max_int: i32 = if (bits == 0) b: { + break :b 0; + } else b: { + break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + }; + if (rhs < min_int or rhs > max_int) { + return self.fail( + node, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, + ); + } + } + } + + return self.sema.pt.intern(.{ .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = rhs }, + } }); + }, + .big => |val| { + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + node, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), val }, + ); + } + } + + return self.sema.pt.intern(.{ .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = val }, + } }); + }, + }, + .float_literal => |val| { + // Check for fractional components + if (@rem(val, 1) != 0) { + return self.fail( + node, + "fractional component prevents float value '{}' from coercion to type '{}'", + .{ val, res_ty.fmt(self.sema.pt) }, + ); + } + + // Create a rational representation of the float + var rational = try std.math.big.Rational.init(self.sema.arena); + rational.setFloat(f128, val) catch |err| switch (err) { + error.NonFiniteFloat => unreachable, + error.OutOfMemory => return error.OutOfMemory, + }; + + // The float is reduced in rational.setFloat, so we assert that denominator is equal to + // one + const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true }; + assert(rational.q.toConst().eqlAbs(big_one)); + + // Check that the result is in range of the result type + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + node, + "type '{}' cannot represent integer value '{}'", + .{ val, res_ty.fmt(self.sema.pt) }, + ); + } + + return self.sema.pt.intern(.{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = rational.p.toConst() }, + }, + }); + }, + .char_literal => |val| { + // If our result is a fixed size integer, check that our value is not out of bounds + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { + const dest_info = res_ty.intInfo(self.sema.pt.zcu); + const unsigned_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed); + if (unsigned_bits < 21) { + const out_of_range: u21 = @as(u21, 1) << @intCast(unsigned_bits); + if (val >= out_of_range) { + return self.fail( + node, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), val }, + ); + } + } + } + return self.sema.pt.intern(.{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = val }, + }, + }); + }, + + else => return error.WrongType, + }; +} + +fn lowerFloat( + self: *LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + const value = switch (node.get(self.file.zoir.?)) { + .int_literal => |int| switch (int) { + .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), + .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)), + }, + .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), + .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), + .pos_inf => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + node, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128)); + }, + .neg_inf => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + node, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128)); + }, + .nan => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + node, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128)); + }, + else => return error.WrongType, + }; + return value.toIntern(); +} + +fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { + switch (node.get(self.file.zoir.?)) { + .null => return .null_value, + else => return error.WrongType, + } +} + +fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const array_info = res_ty.arrayInfo(self.sema.pt.zcu); + const nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + if (nodes.len != array_info.len) { + return error.WrongType; + } + + const elems = try self.sema.arena.alloc( + InternPool.Index, + nodes.len + @intFromBool(array_info.sentinel != null), + ); + + for (0..nodes.len) |i| { + elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type); + } + + if (array_info.sentinel) |sentinel| { + elems[elems.len - 1] = sentinel.toIntern(); + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ .elems = elems }, + } }); +} + +fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + switch (node.get(self.file.zoir.?)) { + .enum_literal => |field_name| { + const field_name_interned = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + field_name.get(self.file.zoir.?), + .no_embedded_nulls, + ); + const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse { + return self.fail( + node, + "enum {} has no member named '{}'", + .{ + res_ty.fmt(self.sema.pt), + std.zig.fmtId(field_name.get(self.file.zoir.?)), + }, + ); + }; + + const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index); + + return value.toIntern(); + }, + else => return error.WrongType, + } +} + +fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + switch (node.get(self.file.zoir.?)) { + .enum_literal => |field_name| { + const field_name_interned = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + field_name.get(self.file.zoir.?), + .no_embedded_nulls, + ); + return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); + }, + else => return error.WrongType, + } +} + +fn lowerStructOrTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + return switch (ip.indexToKey(res_ty.toIntern())) { + .tuple_type => self.lowerTuple(node, res_ty), + .struct_type => self.lowerStruct(node, res_ty), + else => unreachable, + }; +} + +fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + + const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; + + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + const field_types = tuple_info.types.get(ip); + const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len); + + const field_comptime_vals = tuple_info.values.get(ip); + if (field_comptime_vals.len > 0) { + @memcpy(elems, field_comptime_vals); + } else { + @memset(elems, .none); + } + + for (0..elem_nodes.len) |i| { + if (i >= elems.len) { + const elem_node = elem_nodes.at(@intCast(i)); + return self.fail( + elem_node, + "index {} outside tuple of length {}", + .{ + elems.len, + elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?), + }, + ); + } + + const val = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); + + if (elems[i] != .none and val != elems[i]) { + const elem_node = elem_nodes.at(@intCast(i)); + return self.fail( + elem_node, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + + elems[i] = val; + } + + for (elems, 0..) |val, i| { + if (val == .none) { + return self.fail(node, "missing tuple field with index {}", .{i}); + } + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ .elems = elems }, + } }); +} + +fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + + try res_ty.resolveFields(self.sema.pt); + try res_ty.resolveStructFieldInits(self.sema.pt); + const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; + + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { + .struct_literal => |fields| fields, + .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, + else => return error.WrongType, + }; + + const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); + + const field_defaults = struct_info.field_inits.get(ip); + if (field_defaults.len > 0) { + @memcpy(field_values, field_defaults); + } else { + @memset(field_values, .none); + } + + for (0..fields.names.len) |i| { + const field_name = try ip.getOrPutString( + gpa, + self.sema.pt.tid, + fields.names[i].get(self.file.zoir.?), + .no_embedded_nulls, + ); + const field_node = fields.vals.at(@intCast(i)); + + const name_index = struct_info.nameIndex(ip, field_name) orelse { + return self.fail(field_node, "unexpected field '{}'", .{field_name.fmt(ip)}); + }; + + const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]); + field_values[name_index] = try self.lowerExpr(field_node, field_type); + + if (struct_info.comptime_bits.getBit(ip, name_index)) { + const val = ip.indexToKey(field_values[name_index]); + const default = ip.indexToKey(field_defaults[name_index]); + if (!val.eql(default, ip)) { + return self.fail( + field_node, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + } + } + + const field_names = struct_info.field_names.get(ip); + for (field_values, field_names) |*value, name| { + if (value.* == .none) return self.fail(node, "missing field '{}'", .{name.fmt(ip)}); + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ + .elems = field_values, + }, + } }); +} + +fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + + const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); + + assert(ptr_info.flags.size == .slice); + + // String literals + const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1"; + const string_sentinel = ptr_info.sentinel == .none or ptr_info.sentinel == .zero_u8; + if (string_alignment and ptr_info.child == .u8_type and string_sentinel) { + switch (node.get(self.file.zoir.?)) { + .string_literal => |val| { + const ip_str = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls); + const str_ref = try self.sema.addStrLit(ip_str, val.len); + return (try self.sema.coerce( + self.block, + res_ty, + str_ref, + self.nodeSrc(node), + )).toInterned().?; + }, + else => {}, + } + } + + // Slice literals + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); + + for (elems, 0..) |*elem, i| { + elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child)); + } + + if (ptr_info.sentinel != .none) { + elems[elems.len - 1] = ptr_info.sentinel; + } + + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ + .len = elems.len, + .sentinel = ptr_info.sentinel, + .child = ptr_info.child, + } }); + + const array = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty, + .storage = .{ .elems = elems }, + } }); + + const many_item_ptr_type = try self.sema.pt.intern(.{ .ptr_type = .{ + .child = ptr_info.child, + .sentinel = ptr_info.sentinel, + .flags = b: { + var flags = ptr_info.flags; + flags.size = .many; + break :b flags; + }, + .packed_offset = ptr_info.packed_offset, + } }); + + const many_item_ptr = try self.sema.pt.intern(.{ + .ptr = .{ + .ty = many_item_ptr_type, + .base_addr = .{ + .uav = .{ + .orig_ty = (try self.sema.pt.singleConstPtrType(.fromInterned(array_ty))).toIntern(), + .val = array, + }, + }, + .byte_offset = 0, + }, + }); + + const len = (try self.sema.pt.intValue(.usize, elems.len)).toIntern(); + + return self.sema.pt.intern(.{ .slice = .{ + .ty = res_ty.toIntern(), + .ptr = many_item_ptr, + .len = len, + } }); +} + +fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + try res_ty.resolveFields(self.sema.pt); + const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?; + const enum_tag_info = union_info.loadTagType(ip); + + const field_name, const maybe_field_node = switch (node.get(self.file.zoir.?)) { + .enum_literal => |name| b: { + const field_name = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + name.get(self.file.zoir.?), + .no_embedded_nulls, + ); + break :b .{ field_name, null }; + }, + .struct_literal => b: { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { + .struct_literal => |fields| fields, + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + }; + if (fields.names.len != 1) { + return error.WrongType; + } + const field_name = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + fields.names[0].get(self.file.zoir.?), + .no_embedded_nulls, + ); + break :b .{ field_name, fields.vals.at(0) }; + }, + else => return error.WrongType, + }; + + const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { + return error.WrongType; + }; + const tag = try self.sema.pt.enumValueFieldIndex(.fromInterned(union_info.enum_tag_ty), name_index); + const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]); + const val = if (maybe_field_node) |field_node| b: { + if (field_type.toIntern() == .void_type) { + return self.fail(field_node, "expected type 'void'", .{}); + } + break :b try self.lowerExpr(field_node, field_type); + } else b: { + if (field_type.toIntern() != .void_type) { + return error.WrongType; + } + break :b .void_value; + }; + return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ + .ty = res_ty.toIntern(), + .tag = tag.toIntern(), + .val = val, + }); +} + +fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + + const vector_info = ip.indexToKey(res_ty.toIntern()).vector_type; + + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return error.WrongType, + }; + + const elems = try self.sema.arena.alloc(InternPool.Index, vector_info.len); + + if (elem_nodes.len != vector_info.len) { + return self.fail( + node, + "expected {} vector elements; found {}", + .{ vector_info.len, elem_nodes.len }, + ); + } + + for (elems, 0..) |*elem, i| { + elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child)); + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ .elems = elems }, + } }); +} diff --git a/src/Value.zig b/src/Value.zig index c8468c1661bc..f9f552aa0f42 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 74653bce01ae..507452dda34b 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); @@ -672,6 +674,8 @@ pub const File = struct { tree: Ast, /// Whether this is populated or not depends on `zir_loaded`. zir: Zir, + /// Cached Zoir, generated lazily. + zoir: ?Zoir = null, /// Module that this file is a part of, managed externally. mod: *Package.Module, /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen. @@ -704,7 +708,19 @@ pub const File = struct { root: *Package.Module, }; + pub fn getMode(self: File) Ast.Mode { + if (std.mem.endsWith(u8, self.sub_file_path, ".zon")) { + return .zon; + } else if (std.mem.endsWith(u8, self.sub_file_path, ".zig")) { + return .zig; + } else { + // `Module.importFile` rejects all other extensions + unreachable; + } + } + pub fn unload(file: *File, gpa: Allocator) void { + if (file.zoir) |zoir| zoir.deinit(gpa); file.unloadTree(gpa); file.unloadSource(gpa); file.unloadZir(gpa); @@ -778,11 +794,24 @@ pub const File = struct { if (file.tree_loaded) return &file.tree; const source = try file.getSource(gpa); - file.tree = try Ast.parse(gpa, source.bytes, .zig); + file.tree = try Ast.parse(gpa, source.bytes, file.getMode()); file.tree_loaded = true; return &file.tree; } + pub fn getZoir(file: *File, zcu: *Zcu) !*const Zoir { + if (file.zoir) |*zoir| return zoir; + + assert(file.tree_loaded); + assert(file.tree.mode == .zon); + file.zoir = try ZonGen.generate(zcu.gpa, file.tree, .{}); + if (file.zoir.?.hasCompileErrors()) { + try zcu.failed_files.putNoClobber(zcu.gpa, file, null); + return error.AnalysisFail; + } + return &file.zoir.?; + } + pub fn fullyQualifiedNameLen(file: File) usize { const ext = std.fs.path.extension(file.sub_file_path); return file.sub_file_path.len - ext.len; @@ -895,6 +924,7 @@ pub const File = struct { pub const Index = InternPool.FileIndex; }; +/// Represents the contents of a file loaded with `@embedFile`. pub const EmbedFile = struct { /// Module that this file is a part of, managed externally. owner: *Package.Module, @@ -2372,6 +2402,12 @@ pub const LazySrcLoc = struct { break :inst .{ info.file, info.inst }; }; const file = zcu.fileByIndex(file_index); + + // If we're relative to .main_struct_inst, we know the ast node is the root and don't need to resolve the ZIR, + // which may not exist e.g. in the case of errors in ZON files. + if (zir_inst == .main_struct_inst) return .{ file, 0 }; + + // Otherwise, make sure ZIR is loaded. assert(file.zir_loaded); const zir = file.zir; @@ -3461,8 +3497,6 @@ pub fn atomicPtrAlignment( } /// Returns null in the following cases: -/// * `@TypeOf(.{})` -/// * A struct which has no fields (`struct {}`). /// * Not a struct. pub fn typeToStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType { if (ty.ip_index == .none) return null; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2c8dc5480902..d564ef8da5d6 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 205ce46d0aa9..ba5ebf8efd09 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6278,7 +6278,7 @@ fn cmdAstCheck( } }, .zon => { - const zoir = try ZonGen.generate(gpa, file.tree); + const zoir = try ZonGen.generate(gpa, file.tree, .{}); defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { diff --git a/src/print_zir.zig b/src/print_zir.zig index d04ef46c9fd4..033ea82de9cb 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -488,7 +488,6 @@ const Writer = struct { .enum_literal, .decl_ref, .decl_val, - .import, .ret_err_value, .ret_err_value_code, .param_anytype, @@ -515,6 +514,8 @@ const Writer = struct { .declaration => try self.writeDeclaration(stream, inst), .extended => try self.writeExtended(stream, inst), + + .import => try self.writeImport(stream, inst), } } @@ -2842,4 +2843,13 @@ const Writer = struct { try stream.writeByte('\n'); } } + + fn writeImport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok; + const extra = self.code.extraData(Zir.Inst.Import, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.res_ty); + const import_path = self.code.nullTerminatedString(extra.path); + try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(import_path)}); + try self.writeSrcTok(stream, inst_data.src_tok); + } }; diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig new file mode 100644 index 000000000000..2003a8760004 --- /dev/null +++ b/test/behavior/zon.zig @@ -0,0 +1,519 @@ +const std = @import("std"); + +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualDeep = std.testing.expectEqualDeep; +const expectEqualSlices = std.testing.expectEqualSlices; +const expectEqualStrings = std.testing.expectEqualStrings; + +test "bool" { + try expectEqual(true, @as(bool, @import("zon/true.zon"))); + try expectEqual(false, @as(bool, @import("zon/false.zon"))); +} + +test "optional" { + const some: ?u32 = @import("zon/some.zon"); + const none: ?u32 = @import("zon/none.zon"); + const @"null": @TypeOf(null) = @import("zon/none.zon"); + try expectEqual(@as(u32, 10), some); + try expectEqual(@as(?u32, null), none); + try expectEqual(null, @"null"); +} + +test "union" { + // No tag + { + const Union = union { + x: f32, + y: bool, + z: void, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + const union3: Union = @import("zon/union3.zon"); + + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); + } + + // Inferred tag + { + const Union = union(enum) { + x: f32, + y: bool, + z: void, + }; + + const union1: Union = comptime @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + const union3: Union = @import("zon/union3.zon"); + + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); + } + + // Explicit tag + { + const Tag = enum(i128) { + x = -1, + y = 2, + z = 1, + }; + const Union = union(Tag) { + x: f32, + y: bool, + z: void, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + const union3: Union = @import("zon/union3.zon"); + + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); + } +} + +test "struct" { + const Vec0 = struct {}; + const Vec1 = struct { x: f32 }; + const Vec2 = struct { x: f32, y: f32 }; + const Escaped = struct { @"0": f32, foo: f32 }; + try expectEqual(Vec0{}, @as(Vec0, @import("zon/vec0.zon"))); + try expectEqual(Vec1{ .x = 1.5 }, @as(Vec1, @import("zon/vec1.zon"))); + try expectEqual(Vec2{ .x = 1.5, .y = 2 }, @as(Vec2, @import("zon/vec2.zon"))); + try expectEqual(Escaped{ .@"0" = 1.5, .foo = 2 }, @as(Escaped, @import("zon/escaped_struct.zon"))); +} + +test "struct default fields" { + const Vec3 = struct { + x: f32, + y: f32, + z: f32 = 123.4, + }; + try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, @as(Vec3, @import("zon/vec2.zon"))); + const ascribed: Vec3 = @import("zon/vec2.zon"); + try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, ascribed); + + const Vec2 = struct { + x: f32 = 20.0, + y: f32 = 10.0, + }; + try expectEqual(Vec2{ .x = 1.5, .y = 2.0 }, @as(Vec2, @import("zon/vec2.zon"))); +} + +test "struct enum field" { + const Struct = struct { + x: enum { x, y, z }, + }; + try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon"))); +} + +test "tuple" { + const Tuple = struct { f32, bool, []const u8, u16 }; + try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); +} + +test "comptime fields" { + // Test setting comptime tuple fields to the correct value + { + const Tuple = struct { + comptime f32 = 1.2, + comptime bool = true, + comptime []const u8 = "hello", + comptime u16 = 3, + }; + try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); + } + + // Test setting comptime struct fields to the correct value + { + const Vec2 = struct { + comptime x: f32 = 1.5, + comptime y: f32 = 2.0, + }; + try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/vec2.zon"))); + } + + // Test allowing comptime tuple fields to be set to their defaults + { + const Tuple = struct { + f32, + bool, + []const u8, + u16, + comptime u8 = 255, + }; + try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); + } + + // Test allowing comptime struct fields to be set to their defaults + { + const Vec2 = struct { + comptime x: f32 = 1.5, + comptime y: f32 = 2.0, + }; + try expectEqualDeep(Vec2{}, @as(Vec2, @import("zon/slice-empty.zon"))); + } +} + +test "char" { + try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon"))); + try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon"))); +} + +test "arrays" { + try expectEqual([0]u8{}, @as([0]u8, @import("zon/vec0.zon"))); + try expectEqual([0:1]u8{}, @as([0:1]u8, @import("zon/vec0.zon"))); + try expectEqual(1, @as([0:1]u8, @import("zon/vec0.zon"))[0]); + try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @as([4]u8, @import("zon/array.zon"))); + try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @as([4:2]u8, @import("zon/array.zon"))); + try expectEqual(2, @as([4:2]u8, @import("zon/array.zon"))[4]); +} + +test "slices, arrays, tuples" { + { + const expected_slice: []const u8 = &.{}; + const found_slice: []const u8 = @import("zon/slice-empty.zon"); + try expectEqualSlices(u8, expected_slice, found_slice); + + const expected_array: [0]u8 = .{}; + const found_array: [0]u8 = @import("zon/slice-empty.zon"); + try expectEqual(expected_array, found_array); + + const T = struct {}; + const expected_tuple: T = .{}; + const found_tuple: T = @import("zon/slice-empty.zon"); + try expectEqual(expected_tuple, found_tuple); + } + + { + const expected_slice: []const u8 = &.{1}; + const found_slice: []const u8 = @import("zon/slice1_no_newline.zon"); + try expectEqualSlices(u8, expected_slice, found_slice); + + const expected_array: [1]u8 = .{1}; + const found_array: [1]u8 = @import("zon/slice1_no_newline.zon"); + try expectEqual(expected_array, found_array); + + const T = struct { u8 }; + const expected_tuple: T = .{1}; + const found_tuple: T = @import("zon/slice1_no_newline.zon"); + try expectEqual(expected_tuple, found_tuple); + } + + { + const expected_slice: []const u8 = &.{ 'a', 'b', 'c' }; + const found_slice: []const u8 = @import("zon/slice-abc.zon"); + try expectEqualSlices(u8, expected_slice, found_slice); + + const expected_array: [3]u8 = .{ 'a', 'b', 'c' }; + const found_array: [3]u8 = @import("zon/slice-abc.zon"); + try expectEqual(expected_array, found_array); + + const T = struct { u8, u8, u8 }; + const expected_tuple: T = .{ 'a', 'b', 'c' }; + const found_tuple: T = @import("zon/slice-abc.zon"); + try expectEqual(expected_tuple, found_tuple); + } +} + +test "string literals" { + try expectEqualSlices(u8, "abc", @import("zon/abc.zon")); + try expectEqualSlices(u8, "ab\\c", @import("zon/abc-escaped.zon")); + const zero_terminated: [:0]const u8 = @import("zon/abc.zon"); + try expectEqualDeep(zero_terminated, "abc"); + try expectEqual(0, zero_terminated[zero_terminated.len]); + try expectEqualStrings( + \\Hello, world! + \\This is a multiline string! + \\ There are no escapes, we can, for example, include \n in the string + , @import("zon/multiline_string.zon")); + try expectEqualStrings("a\nb\x00c", @import("zon/string_embedded_null.zon")); +} + +test "enum literals" { + const Enum = enum { + foo, + bar, + baz, + @"0\na", + }; + try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon"))); + try expectEqual(.foo, @as(@TypeOf(.foo), @import("zon/foo.zon"))); + try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon"))); +} + +test "int" { + const T = struct { + u8, + i16, + i14, + i32, + i8, + i8, + u8, + u8, + u65, + u65, + i128, + i128, + i66, + i66, + i8, + i8, + i16, + i16, + i16, + i16, + i16, + i16, + u65, + i66, + i66, + u65, + i66, + i66, + u65, + i66, + i66, + }; + const expected: T = .{ + // Test various numbers and types + 10, + 24, + -4, + -123, + + // Test limits + 127, + -128, + + // Test characters + 'a', + 'z', + + // Test big integers + 36893488147419103231, + 36893488147419103231, + -18446744073709551615, // Only a big int due to negation + -9223372036854775809, // Only a big int due to negation + + // Test big integer limits + 36893488147419103231, + -36893488147419103232, + + // Test parsing whole number floats as integers + -1, + 123, + + // Test non-decimal integers + 0xff, + -0xff, + 0o77, + -0o77, + 0b11, + -0b11, + + // Test non-decimal big integers + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + }; + const actual: T = @import("zon/ints.zon"); + try expectEqual(expected, actual); +} + +test "floats" { + const T = struct { + f16, + f32, + f64, + f128, + f16, + f16, + f32, + f32, + f32, + f32, + f32, + f32, + f128, + f32, + f32, + f32, + f32, + f32, + }; + const expected: T = .{ + // Test decimals + 0.5, + 123.456, + -123.456, + 42.5, + + // Test whole numbers with and without decimals + 5.0, + 5.0, + -102, + -102, + + // Test characters and negated characters + 'a', + 'z', + + // Test big integers + 36893488147419103231, + -36893488147419103231, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + + // Exponents, underscores + 123.0E+77, + + // Hexadecimal + 0x103.70p-5, + -0x103.70, + 0x1234_5678.9ABC_CDEFp-10, + }; + const actual: T = @import("zon/floats.zon"); + try expectEqual(expected, actual); +} + +test "inf and nan" { + // f32 + { + const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isPositiveInf(actual[1])); + try expect(std.math.isNegativeInf(actual[2])); + } + + // f128 + { + const actual: struct { f128, f128, f128 } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isPositiveInf(actual[1])); + try expect(std.math.isNegativeInf(actual[2])); + } +} + +test "vector" { + { + const actual: @Vector(0, bool) = @import("zon/vec0.zon"); + const expected: @Vector(0, bool) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, bool) = @import("zon/vec3_bool.zon"); + const expected: @Vector(3, bool) = .{ false, false, true }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, f32) = @import("zon/vec0.zon"); + const expected: @Vector(0, f32) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, f32) = @import("zon/vec3_float.zon"); + const expected: @Vector(3, f32) = .{ 1.5, 2.5, 3.5 }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, u8) = @import("zon/vec3_int.zon"); + const expected: @Vector(3, u8) = .{ 2, 4, 6 }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, *const u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, *const u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, *const u8) = @import("zon/vec3_int.zon"); + const expected: @Vector(3, *const u8) = .{ &2, &4, &6 }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, ?*const u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, ?*const u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, ?*const u8) = @import("zon/vec3_int_opt.zon"); + const expected: @Vector(3, ?*const u8) = .{ &2, null, &6 }; + try expectEqual(expected, actual); + } +} + +test "pointers" { + // Primitive with varying levels of pointers + try expectEqual(@as(u8, 'a'), @as(*const u8, @import("zon/a.zon")).*); + try expectEqual(@as(u8, 'a'), @as(*const *const u8, @import("zon/a.zon")).*.*); + try expectEqual(@as(u8, 'a'), @as(*const *const *const u8, @import("zon/a.zon")).*.*.*); + + // Primitive optional with varying levels of pointers + try expectEqual(@as(u8, 'a'), @as(?*const u8, @import("zon/a.zon")).?.*); + try expectEqual(null, @as(?*const u8, @import("zon/none.zon"))); + + try expectEqual(@as(u8, 'a'), @as(*const ?u8, @import("zon/a.zon")).*.?); + try expectEqual(null, @as(*const ?u8, @import("zon/none.zon")).*); + + try expectEqual(@as(u8, 'a'), @as(?*const *const u8, @import("zon/a.zon")).?.*.*); + try expectEqual(null, @as(?*const *const u8, @import("zon/none.zon"))); + + try expectEqual(@as(u8, 'a'), @as(*const ?*const u8, @import("zon/a.zon")).*.?.*); + try expectEqual(null, @as(*const ?*const u8, @import("zon/none.zon")).*); + + try expectEqual(@as(u8, 'a'), @as(*const *const ?u8, @import("zon/a.zon")).*.*.?); + try expectEqual(null, @as(*const *const ?u8, @import("zon/none.zon")).*.*); + + try expectEqual([3]u8{ 2, 4, 6 }, @as(*const [3]u8, @import("zon/vec3_int.zon")).*); + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const expected: Outer = .{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + const found: ?*const Outer = @import("zon/complex.zon"); + try std.testing.expectEqualDeep(expected, found.?.*); + } +} + +test "recursive" { + const Recursive = struct { foo: ?*const @This() }; + const expected: Recursive = .{ .foo = &.{ .foo = null } }; + try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon"))); +} 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/complex.zon b/test/behavior/zon/complex.zon new file mode 100644 index 000000000000..0d61c25517e1 --- /dev/null +++ b/test/behavior/zon/complex.zon @@ -0,0 +1,7 @@ +.{ + .f1 = .{ + .f1 = null, + .f2 = "foo", + }, + .f2 = null, +} 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/recursive.zon b/test/behavior/zon/recursive.zon new file mode 100644 index 000000000000..063bf84915c0 --- /dev/null +++ b/test/behavior/zon/recursive.zon @@ -0,0 +1 @@ +.{ .foo = .{ .foo = 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/vec3_bool.zon b/test/behavior/zon/vec3_bool.zon new file mode 100644 index 000000000000..53aff974193b --- /dev/null +++ b/test/behavior/zon/vec3_bool.zon @@ -0,0 +1 @@ +.{ false, false, true } diff --git a/test/behavior/zon/vec3_float.zon b/test/behavior/zon/vec3_float.zon new file mode 100644 index 000000000000..a0e225db099e --- /dev/null +++ b/test/behavior/zon/vec3_float.zon @@ -0,0 +1 @@ +.{ 1.5, 2.5, 3.5 } diff --git a/test/behavior/zon/vec3_int.zon b/test/behavior/zon/vec3_int.zon new file mode 100644 index 000000000000..210cd1a2d6c8 --- /dev/null +++ b/test/behavior/zon/vec3_int.zon @@ -0,0 +1 @@ +.{ 2, 4, 6 } diff --git a/test/behavior/zon/vec3_int_opt.zon b/test/behavior/zon/vec3_int_opt.zon new file mode 100644 index 000000000000..9c009b99d6b2 --- /dev/null +++ b/test/behavior/zon/vec3_int_opt.zon @@ -0,0 +1 @@ +.{ 2, null, 6 } 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_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig new file mode 100644 index 000000000000..4942b570728f --- /dev/null +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -0,0 +1,125 @@ +export fn testVoid() void { + const f: void = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInStruct() void { + const f: struct { f: [*]const u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testError() void { + const f: struct { error{foo} } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInUnion() void { + const f: union(enum) { a: void, b: [*c]const u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInVector() void { + const f: @Vector(0, [*c]const u8) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInOpt() void { + const f: *const ?[*c]const u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testComptimeField() void { + const f: struct { comptime foo: ??u8 = null } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testEnumLiteral() void { + const f: @TypeOf(.foo) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt1() void { + const f: ??u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt2() void { + const f: ?*const ?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt3() void { + const f: *const ?*const ?*const u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testOpt() void { + const f: ?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNonExhaustiveEnum() void { + const f: enum(u8) { _ } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testUntaggedUnion() void { + const f: union { foo: void } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testTaggedUnionVoid() void { + const f: union(enum) { foo: void } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testVisited() void { + const V = struct { + ?f32, // Adds `?f32` to the visited list + ??f32, // `?f32` is already visited, we need to detect the nested opt anyway + f32, + }; + const f: V = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testMutablePointer() void { + const f: *i32 = @import("zon/neg_inf.zon"); + _ = f; +} + +// error +// imports=zon/neg_inf.zon +// +// tmp.zig:2:29: error: type 'void' is not available in ZON +// tmp.zig:7:50: error: type '[*]const u8' is not available in ZON +// tmp.zig:7:50: note: ZON does not allow many-pointers +// tmp.zig:12:46: error: type 'error{foo}' is not available in ZON +// tmp.zig:17:65: error: type '[*c]const u8' is not available in ZON +// tmp.zig:17:65: note: ZON does not allow C pointers +// tmp.zig:22:49: error: type '[*c]const u8' is not available in ZON +// tmp.zig:22:49: note: ZON does not allow C pointers +// tmp.zig:27:45: error: type '[*c]const u8' is not available in ZON +// tmp.zig:27:45: note: ZON does not allow C pointers +// tmp.zig:32:61: error: type '??u8' is not available in ZON +// tmp.zig:32:61: note: ZON does not allow nested optionals +// tmp.zig:42:29: error: type '??u8' is not available in ZON +// tmp.zig:42:29: note: ZON does not allow nested optionals +// tmp.zig:47:36: error: type '?*const ?u8' is not available in ZON +// tmp.zig:47:36: note: ZON does not allow nested optionals +// tmp.zig:52:50: error: type '?*const ?*const u8' is not available in ZON +// tmp.zig:52:50: note: ZON does not allow nested optionals +// tmp.zig:82:26: error: type '??f32' is not available in ZON +// tmp.zig:82:26: note: ZON does not allow nested optionals +// tmp.zig:87:29: error: type '*i32' is not available in ZON +// tmp.zig:87:29: note: ZON does not allow mutable pointers +// neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' +// tmp.zig:37:38: note: imported here +// neg_inf.zon:1:1: error: expected type '?u8' +// tmp.zig:57:28: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_490' +// tmp.zig:62:39: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_492' +// tmp.zig:67:44: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_495' +// tmp.zig:72:50: note: imported here diff --git a/test/cases/compile_errors/@import_zon_comptime_inf.zig b/test/cases/compile_errors/@import_zon_comptime_inf.zig new file mode 100644 index 000000000000..0ce8f3681dbc --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_inf.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/inf.zon"); + _ = f; +} + +// error +// imports=zon/inf.zon +// +// inf.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here diff --git a/test/cases/compile_errors/@import_zon_comptime_nan.zig b/test/cases/compile_errors/@import_zon_comptime_nan.zig new file mode 100644 index 000000000000..3c2071523922 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_nan.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/nan.zon"); + _ = f; +} + +// error +// imports=zon/nan.zon +// +// nan.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here diff --git a/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig b/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig new file mode 100644 index 000000000000..dfa665ed193d --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/neg_inf.zon"); + _ = f; +} + +// error +// imports=zon/neg_inf.zon +// +// neg_inf.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here 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_opt_in_err.zig b/test/cases/compile_errors/@import_zon_opt_in_err.zig new file mode 100644 index 000000000000..b7322a43b421 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_opt_in_err.zig @@ -0,0 +1,82 @@ +export fn testFloatA() void { + const f: ?f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testFloatB() void { + const f: *const ?f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testFloatC() void { + const f: ?*const f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testBool() void { + const f: ?bool = @import("zon/vec2.zon"); + _ = f; +} + +export fn testInt() void { + const f: ?i32 = @import("zon/vec2.zon"); + _ = f; +} + +const Enum = enum { foo }; +export fn testEnum() void { + const f: ?Enum = @import("zon/vec2.zon"); + _ = f; +} + +export fn testEnumLit() void { + const f: ?@TypeOf(.foo) = @import("zon/vec2.zon"); + _ = f; +} + +export fn testArray() void { + const f: ?[1]u8 = @import("zon/vec2.zon"); + _ = f; +} + +const Union = union {}; +export fn testUnion() void { + const f: ?Union = @import("zon/vec2.zon"); + _ = f; +} + +export fn testSlice() void { + const f: ?[]const u8 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testVector() void { + const f: ?@Vector(3, f32) = @import("zon/vec2.zon"); + _ = f; +} + +// error +// imports=zon/vec2.zon +// +// vec2.zon:1:2: error: expected type '?f32' +// tmp.zig:2:29: note: imported here +// vec2.zon:1:2: error: expected type '*const ?f32' +// tmp.zig:7:36: note: imported here +// vec2.zon:1:2: error: expected type '?*const f32' +// tmp.zig:12:36: note: imported here +// vec2.zon:1:2: error: expected type '?bool' +// tmp.zig:17:30: note: imported here +// vec2.zon:1:2: error: expected type '?i32' +// tmp.zig:22:29: note: imported here +// vec2.zon:1:2: error: expected type '?tmp.Enum' +// tmp.zig:28:30: note: imported here +// vec2.zon:1:2: error: expected type '?@Type(.enum_literal)' +// tmp.zig:33:39: note: imported here +// vec2.zon:1:2: error: expected type '?[1]u8' +// tmp.zig:38:31: note: imported here +// vec2.zon:1:2: error: expected type '?tmp.Union' +// tmp.zig:44:31: note: imported here +// vec2.zon:1:2: error: expected type '?[]const u8' +// tmp.zig:49:36: note: imported here +// vec2.zon:1:2: error: expected type '?@Vector(3, f32)' +// tmp.zig:54:41: note: imported here diff --git a/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig b/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig new file mode 100644 index 000000000000..a284fd7c6dd6 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig @@ -0,0 +1,19 @@ +const Struct = struct { f: bool }; +export fn testStruct() void { + const f: ?Struct = @import("zon/nan.zon"); + _ = f; +} + +const Tuple = struct { bool }; +export fn testTuple() void { + const f: ?Tuple = @import("zon/nan.zon"); + _ = f; +} + +// error +// imports=zon/nan.zon +// +//nan.zon:1:1: error: expected type '?tmp.Struct' +//tmp.zig:3:32: note: imported here +//nan.zon:1:1: error: expected type '?struct { bool }' +//tmp.zig:9:31: note: imported here diff --git a/test/cases/compile_errors/@import_zon_string_as_array.zig b/test/cases/compile_errors/@import_zon_string_as_array.zig new file mode 100644 index 000000000000..996f239bc46a --- /dev/null +++ b/test/cases/compile_errors/@import_zon_string_as_array.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: [5]u8 = @import("zon/hello.zon"); + _ = f; +} + +// error +// imports=zon/hello.zon +// +// hello.zon:1:1: error: expected type '[5]u8' +// tmp.zig:2:30: note: imported here 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..f8d2771eb5e1 --- /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:2:6: error: duplicate struct field name +// struct_dup_field.zon:3:6: note: duplicate name 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..503c9db33f46 --- /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:19: 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_vec_too_few.zig b/test/cases/compile_errors/@import_zon_vec_too_few.zig new file mode 100644 index 000000000000..a55ce9ca6258 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_too_few.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(3, f32) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// tuple.zon:1:2: error: expected 3 vector elements; found 2 +// tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_too_many.zig b/test/cases/compile_errors/@import_zon_vec_too_many.zig new file mode 100644 index 000000000000..321c26cf0a2f --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_too_many.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(1, f32) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// tuple.zon:1:2: error: expected 1 vector elements; found 2 +// tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_wrong_type.zig b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig new file mode 100644 index 000000000000..ce51dd7101a8 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(2, bool) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// tuple.zon:1:4: error: expected type 'bool' +// tmp.zig:2:41: note: imported here 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/redundant_try.zig b/test/cases/compile_errors/redundant_try.zig index 22dabb1bdbd6..7d02bbd96711 100644 --- a/test/cases/compile_errors/redundant_try.zig +++ b/test/cases/compile_errors/redundant_try.zig @@ -44,9 +44,9 @@ comptime { // // :5:23: error: expected error union type, found 'comptime_int' // :10:23: error: expected error union type, found '@TypeOf(.{})' -// :15:23: error: expected error union type, found 'tmp.test2__struct_493' +// :15:23: error: expected error union type, found 'tmp.test2__struct_494' // :15:23: note: struct declared here -// :20:27: error: expected error union type, found 'tmp.test3__struct_495' +// :20:27: error: expected error union type, found 'tmp.test3__struct_496' // :20:27: note: struct declared here // :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }' // :31:13: error: expected error union type, found 'u32' 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/hello.zon b/test/cases/compile_errors/zon/hello.zon new file mode 100644 index 000000000000..3580093b9da0 --- /dev/null +++ b/test/cases/compile_errors/zon/hello.zon @@ -0,0 +1 @@ +"hello" diff --git a/test/cases/compile_errors/zon/inf.zon b/test/cases/compile_errors/zon/inf.zon new file mode 100644 index 000000000000..a28aa9a0ff05 --- /dev/null +++ b/test/cases/compile_errors/zon/inf.zon @@ -0,0 +1 @@ +inf \ No newline at end of file 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/nan.zon b/test/cases/compile_errors/zon/nan.zon new file mode 100644 index 000000000000..0982929c57fa --- /dev/null +++ b/test/cases/compile_errors/zon/nan.zon @@ -0,0 +1 @@ +nan \ 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_inf.zon b/test/cases/compile_errors/zon/neg_inf.zon new file mode 100644 index 000000000000..f9111418ae47 --- /dev/null +++ b/test/cases/compile_errors/zon/neg_inf.zon @@ -0,0 +1 @@ +-inf \ No newline at end of file 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 b57a476e8938..db1a564373ec 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), @@ -413,6 +418,7 @@ fn addFromDirInner( const pic = try manifest.getConfigForKeyAssertSingle("pic", ?bool); const pie = try manifest.getConfigForKeyAssertSingle("pie", ?bool); const emit_bin = try manifest.getConfigForKeyAssertSingle("emit_bin", bool); + const imports = try manifest.getConfigForKeyAlloc(ctx.arena, "imports", []const u8); if (manifest.type == .translate_c) { for (c_frontends) |c_frontend| { @@ -470,7 +476,7 @@ fn addFromDirInner( const next = ctx.cases.items.len; try ctx.cases.append(.{ .name = std.fs.path.stem(filename), - .target = resolved_target, + .import_path = std.fs.path.dirname(filename), .backend = backend, .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .emit_bin = emit_bin, @@ -480,6 +486,8 @@ fn addFromDirInner( .pic = pic, .pie = pie, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + .imports = imports, + .target = resolved_target, }); try cases.append(next); } @@ -619,6 +627,7 @@ pub fn lowerToBuildSteps( ) void { const host = std.zig.system.resolveTargetQuery(.{}) catch |err| std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)}); + const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); for (self.incremental_cases.items) |incr_case| { if (true) { @@ -662,11 +671,21 @@ pub fn lowerToBuildSteps( file_sources.put(file.path, writefiles.add(file.path, file.src)) catch @panic("OOM"); } + for (case.imports) |import_rel| { + const import_abs = std.fs.path.join(b.allocator, &.{ + cases_dir_path, + case.import_path orelse @panic("import_path not set"), + import_rel, + }) catch @panic("OOM"); + _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel); + } + const mod = b.createModule(.{ .root_source_file = root_source_file, .target = case.target, .optimize = case.optimize_mode, }); + if (case.link_libc) mod.link_libc = true; if (case.pic) |pic| mod.pic = pic; for (case.deps.items) |dep| { @@ -962,6 +981,8 @@ const TestManifestConfigDefaults = struct { return "null"; } else if (std.mem.eql(u8, key, "pie")) { return "null"; + } else if (std.mem.eql(u8, key, "imports")) { + return ""; } else unreachable; } }; @@ -998,6 +1019,7 @@ const TestManifest = struct { .{ "backend", {} }, .{ "pic", {} }, .{ "pie", {} }, + .{ "imports", {} }, }); const Type = enum { @@ -1020,7 +1042,7 @@ const TestManifest = struct { fn ConfigValueIterator(comptime T: type) type { return struct { - inner: std.mem.SplitIterator(u8, .scalar), + inner: std.mem.TokenIterator(u8, .scalar), fn next(self: *@This()) !?T { const next_raw = self.inner.next() orelse return null; @@ -1098,7 +1120,9 @@ const TestManifest = struct { // Parse key=value(s) var kv_it = std.mem.splitScalar(u8, trimmed, '='); const key = kv_it.first(); - if (!valid_keys.has(key)) return error.InvalidKey; + if (!valid_keys.has(key)) { + return error.InvalidKey; + } try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); } @@ -1115,7 +1139,7 @@ const TestManifest = struct { ) ConfigValueIterator(T) { const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); return ConfigValueIterator(T){ - .inner = std.mem.splitScalar(u8, bytes, ','), + .inner = std.mem.tokenizeScalar(u8, bytes, ','), }; } @@ -1232,6 +1256,18 @@ const TestManifest = struct { return try getDefaultParser(o.child)(str); } }.parse, + .@"struct" => @compileError("no default parser for " ++ @typeName(T)), + .pointer => { + if (T == []const u8) { + return struct { + fn parse(str: []const u8) anyerror!T { + return str; + } + }.parse; + } else { + @compileError("no default parser for " ++ @typeName(T)); + } + }, else => @compileError("no default parser for " ++ @typeName(T)), } } From dc5c8278474f998360bc48e3dd0fe9a2929b4374 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 3 Feb 2025 09:12:20 +0000 Subject: [PATCH 2/2] std.heap.GeneralPurposeAllocator: disable some tests on wasm32-wasi The ZON PR (#20271) is causing these tests to inexplicably fail. It doesn't seem like that PR is what's breaking GPA, so these tests are now disabled. This is tracked by #22731. --- lib/std/heap/general_purpose_allocator.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index 6f1adad8fbfe..c23f8dcd7983 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -1235,6 +1235,8 @@ test "shrink large object to large object" { } test "shrink large object to large object with larger alignment" { + if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731 + var gpa = GeneralPurposeAllocator(test_config){}; defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); const allocator = gpa.allocator(); @@ -1307,6 +1309,8 @@ test "non-page-allocator backing allocator" { } test "realloc large object to larger alignment" { + if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731 + var gpa = GeneralPurposeAllocator(test_config){}; defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); const allocator = gpa.allocator();